Skip to content

Commit

Permalink
Focus improvements (#574)
Browse files Browse the repository at this point in the history
* New approach for metering

* Include AWB

* Complete AWB integration

* Rearrange code into MeteringParameters objects

* Add FORCED_END_DELAY into Meter

* Small changes

* Improve Camera2 pictures speed and quality

* Extend auto focus functionality to more cameras

* Move Mapper to own package

* Refactor Camera1Mapper

* Refactor Camera2Mapper

* Rename mapper methods

* Add Camera2MapperTests

* Fix success parameter

* Fix focus when zooming
  • Loading branch information
natario1 authored Aug 30, 2019
1 parent 0731b64 commit a8fddc4
Show file tree
Hide file tree
Showing 19 changed files with 1,292 additions and 527 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import android.hardware.Camera;

import com.otaliastudios.cameraview.controls.Audio;
import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.engine.Mapper;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
Expand Down Expand Up @@ -252,11 +251,11 @@ public void testFacing() {
}

CameraOptions o = new CameraOptions(mock(Camera.Parameters.class), false);
Mapper m = Mapper.get(Engine.CAMERA1);
Camera1Mapper m = Camera1Mapper.get();
Collection<Facing> s = o.getSupportedControls(Facing.class);
assertEquals(s.size(), supported.size());
for (Facing facing : s) {
assertTrue(supported.contains(m.<Integer>map(facing)));
assertTrue(supported.contains(m.mapFacing(facing)));
assertTrue(o.supports(facing));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.otaliastudios.cameraview.engine;
package com.otaliastudios.cameraview.engine.mappers;


import android.hardware.Camera;

import com.otaliastudios.cameraview.BaseTest;
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 com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
Expand All @@ -21,28 +21,28 @@

@RunWith(AndroidJUnit4.class)
@SmallTest
public class Mapper1Test extends BaseTest {
public class Camera1MapperTest extends BaseTest {

private Mapper mapper = Mapper.get(Engine.CAMERA1);
private Camera1Mapper mapper = Camera1Mapper.get();

@Test
public void testMap() {
assertEquals(mapper.map(Flash.OFF), Camera.Parameters.FLASH_MODE_OFF);
assertEquals(mapper.map(Flash.ON), Camera.Parameters.FLASH_MODE_ON);
assertEquals(mapper.map(Flash.AUTO), Camera.Parameters.FLASH_MODE_AUTO);
assertEquals(mapper.map(Flash.TORCH), Camera.Parameters.FLASH_MODE_TORCH);

assertEquals(mapper.map(Facing.BACK), Camera.CameraInfo.CAMERA_FACING_BACK);
assertEquals(mapper.map(Facing.FRONT), Camera.CameraInfo.CAMERA_FACING_FRONT);

assertEquals(mapper.map(Hdr.OFF), Camera.Parameters.SCENE_MODE_AUTO);
assertEquals(mapper.map(Hdr.ON), Camera.Parameters.SCENE_MODE_HDR);

assertEquals(mapper.map(WhiteBalance.AUTO), Camera.Parameters.WHITE_BALANCE_AUTO);
assertEquals(mapper.map(WhiteBalance.DAYLIGHT), Camera.Parameters.WHITE_BALANCE_DAYLIGHT);
assertEquals(mapper.map(WhiteBalance.CLOUDY), Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT);
assertEquals(mapper.map(WhiteBalance.INCANDESCENT), Camera.Parameters.WHITE_BALANCE_INCANDESCENT);
assertEquals(mapper.map(WhiteBalance.FLUORESCENT), Camera.Parameters.WHITE_BALANCE_FLUORESCENT);
assertEquals(mapper.mapFlash(Flash.OFF), Camera.Parameters.FLASH_MODE_OFF);
assertEquals(mapper.mapFlash(Flash.ON), Camera.Parameters.FLASH_MODE_ON);
assertEquals(mapper.mapFlash(Flash.AUTO), Camera.Parameters.FLASH_MODE_AUTO);
assertEquals(mapper.mapFlash(Flash.TORCH), Camera.Parameters.FLASH_MODE_TORCH);

assertEquals(mapper.mapFacing(Facing.BACK), Camera.CameraInfo.CAMERA_FACING_BACK);
assertEquals(mapper.mapFacing(Facing.FRONT), Camera.CameraInfo.CAMERA_FACING_FRONT);

assertEquals(mapper.mapHdr(Hdr.OFF), Camera.Parameters.SCENE_MODE_AUTO);
assertEquals(mapper.mapHdr(Hdr.ON), Camera.Parameters.SCENE_MODE_HDR);

assertEquals(mapper.mapWhiteBalance(WhiteBalance.AUTO), Camera.Parameters.WHITE_BALANCE_AUTO);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.DAYLIGHT), Camera.Parameters.WHITE_BALANCE_DAYLIGHT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.CLOUDY), Camera.Parameters.WHITE_BALANCE_CLOUDY_DAYLIGHT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.INCANDESCENT), Camera.Parameters.WHITE_BALANCE_INCANDESCENT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.FLUORESCENT), Camera.Parameters.WHITE_BALANCE_FLUORESCENT);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.otaliastudios.cameraview.engine.mappers;


import android.hardware.Camera;
import android.util.Pair;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.otaliastudios.cameraview.BaseTest;
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 org.junit.Test;
import org.junit.runner.RunWith;

import java.util.List;
import java.util.Set;

import static android.hardware.camera2.CameraMetadata.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;



@RunWith(AndroidJUnit4.class)
@SmallTest
public class Camera2MapperTest extends BaseTest {

private Camera2Mapper mapper = Camera2Mapper.get();

@Test
public void testMap() {
List<Pair<Integer, Integer>> values = mapper.mapFlash(Flash.OFF);
assertEquals(2, values.size());
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_OFF)));
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_OFF)));
values = mapper.mapFlash(Flash.TORCH);
assertEquals(2, values.size());
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON, FLASH_MODE_TORCH)));
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_OFF, FLASH_MODE_TORCH)));
values = mapper.mapFlash(Flash.AUTO);
assertEquals(2, values.size());
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH, FLASH_MODE_OFF)));
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE, FLASH_MODE_OFF)));
values = mapper.mapFlash(Flash.ON);
assertEquals(1, values.size());
assertTrue(values.contains(new Pair<>(CONTROL_AE_MODE_ON_ALWAYS_FLASH, FLASH_MODE_OFF)));

assertEquals(mapper.mapFacing(Facing.BACK), LENS_FACING_BACK);
assertEquals(mapper.mapFacing(Facing.FRONT), LENS_FACING_FRONT);

assertEquals(mapper.mapHdr(Hdr.OFF), CONTROL_SCENE_MODE_DISABLED);
assertEquals(mapper.mapHdr(Hdr.ON), CONTROL_SCENE_MODE_HDR);

assertEquals(mapper.mapWhiteBalance(WhiteBalance.AUTO), CONTROL_AWB_MODE_AUTO);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.DAYLIGHT), CONTROL_AWB_MODE_DAYLIGHT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.CLOUDY), CONTROL_AWB_MODE_CLOUDY_DAYLIGHT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.INCANDESCENT), CONTROL_AWB_MODE_INCANDESCENT);
assertEquals(mapper.mapWhiteBalance(WhiteBalance.FLUORESCENT), CONTROL_AWB_MODE_FLUORESCENT);
}


@Test
public void testUnmap() {
Set<Flash> values;
values = mapper.unmapFlash(CONTROL_AE_MODE_OFF);
assertEquals(values.size(), 2);
assertTrue(values.contains(Flash.OFF));
assertTrue(values.contains(Flash.TORCH));
values = mapper.unmapFlash(CONTROL_AE_MODE_ON);
assertEquals(values.size(), 2);
assertTrue(values.contains(Flash.OFF));
assertTrue(values.contains(Flash.TORCH));
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_ALWAYS_FLASH);
assertEquals(values.size(), 1);
assertTrue(values.contains(Flash.ON));
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH);
assertEquals(values.size(), 1);
assertTrue(values.contains(Flash.AUTO));
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
assertEquals(values.size(), 1);
assertTrue(values.contains(Flash.AUTO));
values = mapper.unmapFlash(CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
assertEquals(values.size(), 0);

assertEquals(Facing.BACK, mapper.unmapFacing(LENS_FACING_BACK));
assertEquals(Facing.FRONT, mapper.unmapFacing(LENS_FACING_FRONT));

assertEquals(Hdr.OFF, mapper.unmapHdr(CONTROL_SCENE_MODE_DISABLED));
assertEquals(Hdr.ON, mapper.unmapHdr(CONTROL_SCENE_MODE_HDR));

assertEquals(WhiteBalance.AUTO, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_AUTO));
assertEquals(WhiteBalance.DAYLIGHT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_DAYLIGHT));
assertEquals(WhiteBalance.CLOUDY, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_CLOUDY_DAYLIGHT));
assertEquals(WhiteBalance.INCANDESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_INCANDESCENT));
assertEquals(WhiteBalance.FLUORESCENT, mapper.unmapWhiteBalance(CONTROL_AWB_MODE_FLUORESCENT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.engine.Mapper;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper;
import com.otaliastudios.cameraview.gesture.GestureAction;
import com.otaliastudios.cameraview.controls.Grid;
import com.otaliastudios.cameraview.controls.Hdr;
Expand Down Expand Up @@ -63,7 +64,7 @@ public class CameraOptions {

public CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) {
List<String> strings;
Mapper mapper = Mapper.get(Engine.CAMERA1);
Camera1Mapper mapper = Camera1Mapper.get();

// Facing
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Expand Down Expand Up @@ -148,7 +149,7 @@ public CameraOptions(@NonNull Camera.Parameters params, boolean flipSizes) {
// Camera2Engine constructor.
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public CameraOptions(@NonNull CameraManager manager, @NonNull String cameraId, boolean flipSizes) throws CameraAccessException {
Mapper mapper = Mapper.get(Engine.CAMERA2);
Camera2Mapper mapper = Camera2Mapper.get();
CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId);

// Facing
Expand Down Expand Up @@ -176,14 +177,8 @@ public CameraOptions(@NonNull CameraManager manager, @NonNull String cameraId,
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<Flash> flashes = mapper.unmapFlash(aeMode);
supportedFlash.addAll(flashes);
}
}

Expand All @@ -196,22 +191,24 @@ public CameraOptions(@NonNull CameraManager manager, @NonNull String cameraId,
if (value != null) supportedHdr.add(value);
}

//zoom
// Zoom
Float maxZoom = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
if(maxZoom != null) {
zoomSupported = maxZoom > 1;
}


// 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;
}
}
// AutoFocus
// This now means 3A metering with respect to a specific region of the screen.
// Some controls (AF, AE) have special triggers that might or might not be supported.
// But they can also be on some continuous search mode so that the trigger is not needed.
// What really matters in my opinion is the availability of regions.
Integer afRegions = cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
Integer aeRegions = cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
Integer awbRegions = cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB);
autoFocusSupported = (afRegions != null && afRegions > 0)
|| (aeRegions != null && aeRegions > 0)
|| (awbRegions != null && awbRegions > 0);

// Exposure correction
Range<Integer> exposureRange = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
Expand Down Expand Up @@ -430,8 +427,9 @@ public boolean isZoomSupported() {


/**
* Whether auto focus is supported. This means you can map gestures to
* {@link GestureAction#AUTO_FOCUS} and focus will be changed on tap.
* Whether auto focus (metering with respect to a specific region of the screen) is
* supported. If it is, you can map gestures to {@link GestureAction#AUTO_FOCUS}
* and metering will change on tap.
*
* @return whether auto focus is supported.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
import android.hardware.Camera;
import android.os.Handler;

import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.engine.Mapper;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;

Expand Down Expand Up @@ -61,7 +60,7 @@ public static boolean hasCameras(@NonNull Context context) {
*/
public static boolean hasCameraFacing(@SuppressWarnings("unused") @NonNull Context context,
@NonNull Facing facing) {
int internal = Mapper.get(Engine.CAMERA1).map(facing);
int internal = Camera1Mapper.get().mapFacing(facing);
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, cameraInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.controls.Engine;
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
import com.otaliastudios.cameraview.engine.offset.Axis;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.frame.Frame;
Expand Down Expand Up @@ -61,13 +61,13 @@ public class Camera1Engine extends CameraEngine implements
private static final int PREVIEW_FORMAT = ImageFormat.NV21;
@VisibleForTesting static final int AUTOFOCUS_END_DELAY_MILLIS = 2500;

private final Camera1Mapper mMapper = Camera1Mapper.get();
private Camera mCamera;
@VisibleForTesting int mCameraId;
private Runnable mFocusEndRunnable;

public Camera1Engine(@NonNull Callback callback) {
super(callback);
mMapper = Mapper.get(Engine.CAMERA1);
}

//region Utilities
Expand Down Expand Up @@ -117,7 +117,7 @@ protected void onPreviewStreamSizeChanged() {

@Override
protected boolean collectCameraInfo(@NonNull Facing facing) {
int internalFacing = mMapper.map(facing);
int internalFacing = mMapper.mapFacing(facing);
LOG.i("collectCameraInfo", "Facing:", facing, "Internal:", internalFacing, "Cameras:", Camera.getNumberOfCameras());
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Expand Down Expand Up @@ -443,7 +443,7 @@ public void run() {

private boolean applyFlash(@NonNull Camera.Parameters params, @NonNull Flash oldFlash) {
if (mCameraOptions.supports(mFlash)) {
params.setFlashMode((String) mMapper.map(mFlash));
params.setFlashMode(mMapper.mapFlash(mFlash));
return true;
}
mFlash = oldFlash;
Expand Down Expand Up @@ -496,7 +496,7 @@ public void run() {

private boolean applyWhiteBalance(@NonNull Camera.Parameters params, @NonNull WhiteBalance oldWhiteBalance) {
if (mCameraOptions.supports(mWhiteBalance)) {
params.setWhiteBalance((String) mMapper.map(mWhiteBalance));
params.setWhiteBalance(mMapper.mapWhiteBalance(mWhiteBalance));
return true;
}
mWhiteBalance = oldWhiteBalance;
Expand All @@ -521,7 +521,7 @@ public void run() {

private boolean applyHdr(@NonNull Camera.Parameters params, @NonNull Hdr oldHdr) {
if (mCameraOptions.supports(mHdr)) {
params.setSceneMode((String) mMapper.map(mHdr));
params.setSceneMode(mMapper.mapHdr(mHdr));
return true;
}
mHdr = oldHdr;
Expand Down
Loading

0 comments on commit a8fddc4

Please sign in to comment.