Set accurate fps if possible (#754)

* Feature option to setPreviewFrameRate as exact as possible

* update docs for setPreviewFrameRateExact

* clean code and add tests for feature setPreviewFrameRateExact

* fix test issue and clean code for setPreviewFrameRateExact

* fix accessiblility issue for mPreviewFrameRateExact

* fix test issue for testPreviewFrameRateExact

Co-authored-by: Mattia Iavarone <mat.iavarone@gmail.com>
pull/759/head
hualong-shen 5 years ago committed by Mattia Iavarone
parent e207e452bf
commit 24b02caa28
  1. 1
      README.md
  2. 9
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  3. 35
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  4. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  5. 23
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  6. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java
  7. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  8. 1
      cameraview/src/main/res/values/attrs.xml
  9. 15
      docs/_docs/controls.md

@ -129,6 +129,7 @@ Using CameraView is extremely simple:
app:cameraEngine="camera1|camera2"
app:cameraPreview="glSurface|surface|texture"
app:cameraPreviewFrameRate="@integer/preview_frame_rate"
app:cameraPreviewFrameRateExact="false|true"
app:cameraFacing="back|front"
app:cameraHdr="on|off"
app:cameraFlash="on|auto|torch|off"

@ -177,6 +177,7 @@ public class CameraViewTest extends BaseTest {
assertEquals(cameraView.getFrameProcessingMaxWidth(), 0);
assertEquals(cameraView.getFrameProcessingMaxHeight(), 0);
assertEquals(cameraView.getFrameProcessingFormat(), 0);
assertFalse(cameraView.getPreviewFrameRateExact());
// Self managed
GestureParser gestures = new GestureParser(empty);
@ -833,6 +834,14 @@ public class CameraViewTest extends BaseTest {
assertEquals(cameraView.getPreviewFrameRate(), 60, 0);
}
@Test
public void testPreviewFrameRateExact() {
cameraView.setPreviewFrameRateExact(true);
assertTrue(cameraView.getPreviewFrameRateExact());
cameraView.setPreviewFrameRateExact(false);
assertFalse(cameraView.getPreviewFrameRateExact());
}
@Test
public void testSnapshotMaxSize() {
cameraView.setSnapshotMaxWidth(500);

@ -206,6 +206,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
int videoBitRate = a.getInteger(R.styleable.CameraView_cameraVideoBitRate, 0);
int audioBitRate = a.getInteger(R.styleable.CameraView_cameraAudioBitRate, 0);
float videoFrameRate = a.getFloat(R.styleable.CameraView_cameraPreviewFrameRate, 0);
boolean videoFrameRateExact = a.getBoolean(R.styleable.CameraView_cameraPreviewFrameRateExact, false);
long autoFocusResetDelay = (long) a.getInteger(
R.styleable.CameraView_cameraAutoFocusResetDelay,
(int) DEFAULT_AUTOFOCUS_RESET_DELAY_MILLIS);
@ -277,6 +278,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setVideoMaxDuration(videoMaxDuration);
setVideoBitRate(videoBitRate);
setAutoFocusResetDelay(autoFocusResetDelay);
setPreviewFrameRateExact(videoFrameRateExact);
setPreviewFrameRate(videoFrameRate);
setSnapshotMaxWidth(snapshotMaxWidth);
setSnapshotMaxHeight(snapshotMaxHeight);
@ -999,6 +1001,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setVideoBitRate(oldEngine.getVideoBitRate());
setAutoFocusResetDelay(oldEngine.getAutoFocusResetDelay());
setPreviewFrameRate(oldEngine.getPreviewFrameRate());
setPreviewFrameRateExact(oldEngine.getPreviewFrameRateExact());
setSnapshotMaxWidth(oldEngine.getSnapshotMaxWidth());
setSnapshotMaxHeight(oldEngine.getSnapshotMaxHeight());
setFrameProcessingMaxWidth(oldEngine.getFrameProcessingMaxWidth());
@ -1543,6 +1546,38 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
return mCameraEngine.getVideoBitRate();
}
/**
* A flag to control the behavior when calling {@link #setPreviewFrameRate(float)}.
*
* If the value is set to true, {@link #setPreviewFrameRate(float)} will choose the preview
* frame range as close to the desired new frame rate as possible. Which mean it may choose a
* narrow range around the desired frame rate. Note: This option will give you as exact fps as
* you want but the sensor will have less freedom when adapting the exposure to the environment,
* which may lead to dark preview.
*
* If the value is set to false, {@link #setPreviewFrameRate(float)} will choose as broad range
* as it can.
*
* @param videoFrameRateExact whether want a more exact preview frame range
*
* @see #setPreviewFrameRate(float)
*/
public void setPreviewFrameRateExact(boolean videoFrameRateExact) {
mCameraEngine.setPreviewFrameRateExact(videoFrameRateExact);
}
/**
* Returns whether we want to set preview fps as exact as we set through
* {@link #setPreviewFrameRate(float)}.
*
* @see #setPreviewFrameRateExact(boolean)
* @see #setPreviewFrameRate(float)
* @return current option
*/
public boolean getPreviewFrameRateExact() {
return mCameraEngine.getPreviewFrameRateExact();
}
/**
* Sets the preview frame rate in frames per second.
* This rate will be used, for example, by the frame processor and in video

@ -52,6 +52,7 @@ import com.otaliastudios.cameraview.video.SnapshotVideoRecorder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@ -715,6 +716,7 @@ public class Camera1Engine extends CameraBaseEngine implements
private boolean applyPreviewFrameRate(@NonNull Camera.Parameters params,
float oldPreviewFrameRate) {
List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
sortRanges(fpsRanges);
if (mPreviewFrameRate == 0F) {
// 0F is a special value. Fallback to a reasonable default.
for (int[] fpsRange : fpsRanges) {
@ -745,6 +747,24 @@ public class Camera1Engine extends CameraBaseEngine implements
return false;
}
private void sortRanges(List<int[]> fpsRanges) {
if (getPreviewFrameRateExact() && mPreviewFrameRate != 0F) { // sort by range width in ascending order
Collections.sort(fpsRanges, new Comparator<int[]>() {
@Override
public int compare(int[] range1, int[] range2) {
return (range1[1] - range1[0]) - (range2[1] - range2[0]);
}
});
} else { // sort by range width in descending order
Collections.sort(fpsRanges, new Comparator<int[]>() {
@Override
public int compare(int[] range1, int[] range2) {
return (range2[1] - range2[0]) - (range1[1] - range1[0]);
}
});
}
}
@Override
public void setPictureFormat(@NonNull PictureFormat pictureFormat) {
if (pictureFormat != PictureFormat.JPEG) {

@ -71,6 +71,8 @@ import com.otaliastudios.cameraview.video.Full2VideoRecorder;
import com.otaliastudios.cameraview.video.SnapshotVideoRecorder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
@ -1378,6 +1380,7 @@ public class Camera2Engine extends CameraBaseEngine implements
Range<Integer>[] fpsRanges = readCharacteristic(
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
fallback);
sortRanges(fpsRanges);
if (mPreviewFrameRate == 0F) {
// 0F is a special value. Fallback to a reasonable default.
for (Range<Integer> fpsRange : fpsRanges) {
@ -1403,6 +1406,26 @@ public class Camera2Engine extends CameraBaseEngine implements
return false;
}
private void sortRanges(Range<Integer>[] fpsRanges) {
if (getPreviewFrameRateExact() && mPreviewFrameRate != 0) { // sort by range width in ascending order
Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() {
@Override
public int compare(Range<Integer> range1, Range<Integer> range2) {
return (range1.getUpper() - range1.getLower())
- (range2.getUpper() - range2.getLower());
}
});
} else { // sort by range width in descending order
Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() {
@Override
public int compare(Range<Integer> range1, Range<Integer> range2) {
return (range2.getUpper() - range2.getLower())
- (range1.getUpper() - range1.getLower());
}
});
}
}
@Override
public void setPictureFormat(final @NonNull PictureFormat pictureFormat) {
if (pictureFormat != mPictureFormat) {

@ -70,6 +70,7 @@ public abstract class CameraBaseEngine extends CameraEngine {
@SuppressWarnings("WeakerAccess") protected boolean mPictureMetering;
@SuppressWarnings("WeakerAccess") protected boolean mPictureSnapshotMetering;
@SuppressWarnings("WeakerAccess") protected float mPreviewFrameRate;
@SuppressWarnings("WeakerAccess") private boolean mPreviewFrameRateExact;
private FrameManager mFrameManager;
private final Angles mAngles = new Angles();
@ -441,6 +442,16 @@ public abstract class CameraBaseEngine extends CameraEngine {
return mPictureFormat;
}
@Override
public final void setPreviewFrameRateExact(boolean previewFrameRateExact) {
mPreviewFrameRateExact = previewFrameRateExact;
}
@Override
public final boolean getPreviewFrameRateExact() {
return mPreviewFrameRateExact;
}
@Override
public final float getPreviewFrameRate() {
return mPreviewFrameRate;

@ -690,6 +690,8 @@ public abstract class CameraEngine implements
public abstract void setPictureFormat(@NonNull PictureFormat pictureFormat);
@NonNull public abstract PictureFormat getPictureFormat();
public abstract void setPreviewFrameRateExact(boolean previewFrameRateExact);
public abstract boolean getPreviewFrameRateExact();
public abstract void setPreviewFrameRate(float previewFrameRate);
public abstract float getPreviewFrameRate();

@ -34,6 +34,7 @@
<attr name="cameraVideoBitRate" format="integer|reference" />
<attr name="cameraAudioBitRate" format="integer|reference" />
<attr name="cameraPreviewFrameRate" format="integer|reference" />
<attr name="cameraPreviewFrameRateExact" format="boolean" />
<attr name="cameraGestureTap" format="enum">
<enum name="none" value="0" />

@ -29,7 +29,8 @@ or `CameraOptions.supports(Control)` to see if it is supported.
app:cameraVideoMaxSize="0"
app:cameraVideoMaxDuration="0"
app:cameraVideoBitRate="0"
app:cameraPreviewFrameRate="30"/>
app:cameraPreviewFrameRate="30"
app:cameraPreviewFrameRateExact="false|true"/>
```
### APIs
@ -180,6 +181,18 @@ float min = options.getPreviewFrameRateMinValue();
float max = options.getPreviewFrameRateMaxValue();
```
##### cameraPreviewFrameRateExact
Controls the behavior of `cameraPreviewFrameRate`. If this option is set to `true`, the narrowest
range containing the new preview fps will be used. If this option is set to `false` the broadest
range containing the new preview fps will be used. Note: If set this option to true, it will give as
exact preview fps as you want, but the sensor will have less freedom when adapting the exposure to
the environment, which may lead to dark preview.
```java
cameraView.setPreviewFrameRateExact(true);
cameraView.setPreviewFrameRageExact(false);
```
### Zoom
There are two ways to control the zoom value:

Loading…
Cancel
Save