Preview FPS docs + Camera1 implementation (#661)

* Small changes

* Camera1 options

* Complete Camera1 integration

* Add to demo app

* Complete docs
pull/662/head
Mattia Iavarone 5 years ago committed by GitHub
parent fa88783d37
commit 218aa9d108
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      README.md
  2. 14
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
  3. 64
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  4. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  5. 53
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  6. 26
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  7. 11
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  8. 43
      demo/src/main/java/com/otaliastudios/cameraview/demo/Option.java
  9. 36
      docs/_posts/2018-12-20-controls.md

@ -122,6 +122,7 @@ Using CameraView is extremely simple:
app:cameraGestureScrollVertical="none|zoom|exposureCorrection|filterControl1|filterControl2"
app:cameraEngine="camera1|camera2"
app:cameraPreview="glSurface|surface|texture"
app:cameraPreviewFrameRate="@integer/preview_frame_rate"
app:cameraFacing="back|front"
app:cameraHdr="on|off"
app:cameraFlash="on|auto|torch|off"

@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -345,4 +346,17 @@ public class CameraOptions1Test extends BaseTest {
assertEquals(o.getExposureCorrectionMaxValue(), 10f * 0.5f, 0f);
}
@Test
public void testPreviewFrameRate() {
Camera.Parameters params = mock(Camera.Parameters.class);
List<int[]> result = Arrays.asList(
new int[]{20000, 30000},
new int[]{30000, 60000},
new int[]{60000, 120000}
);
when(params.getSupportedPreviewFpsRange()).thenReturn(result);
CameraOptions o = new CameraOptions(params, 0, false);
assertEquals(20F, o.getPreviewFrameRateMinValue(), 0.001F);
assertEquals(120F, o.getPreviewFrameRateMaxValue(), 0.001F);
}
}

@ -60,8 +60,8 @@ public class CameraOptions {
private float exposureCorrectionMinValue;
private float exposureCorrectionMaxValue;
private boolean autoFocusSupported;
private float fpsRangeMinValue;
private float fpsRangeMaxValue;
private float previewFrameRateMinValue;
private float previewFrameRateMaxValue;
public CameraOptions(@NonNull Camera.Parameters params, int cameraId, boolean flipSizes) {
@ -159,9 +159,16 @@ public class CameraOptions {
}
}
//fps range
fpsRangeMinValue = 0F;
fpsRangeMaxValue = 0F;
// Preview FPS
previewFrameRateMinValue = Float.MAX_VALUE;
previewFrameRateMaxValue = -Float.MAX_VALUE;
List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
for (int[] fpsRange : fpsRanges) {
float lower = (float) fpsRange[0] / 1000F;
float upper = (float) fpsRange[1] / 1000F;
previewFrameRateMinValue = Math.min(previewFrameRateMinValue, lower);
previewFrameRateMaxValue = Math.max(previewFrameRateMaxValue, upper);
}
}
// Camera2Engine constructor.
@ -279,22 +286,19 @@ public class CameraOptions {
}
}
//fps Range
fpsRangeMinValue = Float.MAX_VALUE;
fpsRangeMaxValue = Float.MIN_VALUE;
Range<Integer>[] range = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
// Preview FPS
Range<Integer>[] range = cameraCharacteristics.get(
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if (range != null) {
previewFrameRateMinValue = Float.MAX_VALUE;
previewFrameRateMaxValue = -Float.MAX_VALUE;
for (Range<Integer> fpsRange : range) {
if (fpsRange.getLower() <= fpsRangeMinValue) {
fpsRangeMinValue = fpsRange.getLower();
}
if (fpsRange.getUpper() >= fpsRangeMaxValue) {
fpsRangeMaxValue = fpsRange.getUpper();
}
previewFrameRateMinValue = Math.min(previewFrameRateMinValue, fpsRange.getLower());
previewFrameRateMaxValue = Math.max(previewFrameRateMaxValue, fpsRange.getUpper());
}
} else {
fpsRangeMinValue = 0F;
fpsRangeMaxValue = 0F;
previewFrameRateMinValue = 0F;
previewFrameRateMaxValue = 0F;
}
}
@ -308,7 +312,6 @@ public class CameraOptions {
return getSupportedControls(control.getClass()).contains(control);
}
/**
* Shorthand for other methods in this class,
* e.g. supports(GestureAction.ZOOM) == isZoomSupported().
@ -333,7 +336,6 @@ public class CameraOptions {
return false;
}
@SuppressWarnings("unchecked")
@NonNull
public <T extends Control> Collection<T> getSupportedControls(@NonNull Class<T> controlClass) {
@ -362,7 +364,6 @@ public class CameraOptions {
return Collections.emptyList();
}
/**
* Set of supported picture sizes for the currently opened camera.
*
@ -373,7 +374,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedPictureSizes);
}
/**
* Set of supported picture aspect ratios for the currently opened camera.
*
@ -385,7 +385,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedPictureAspectRatio);
}
/**
* Set of supported video sizes for the currently opened camera.
*
@ -396,7 +395,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedVideoSizes);
}
/**
* Set of supported picture aspect ratios for the currently opened camera.
*
@ -408,7 +406,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedVideoAspectRatio);
}
/**
* Set of supported facing values.
*
@ -421,7 +418,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedFacing);
}
/**
* Set of supported flash values.
*
@ -436,7 +432,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedFlash);
}
/**
* Set of supported white balance values.
*
@ -452,7 +447,6 @@ public class CameraOptions {
return Collections.unmodifiableSet(supportedWhiteBalance);
}
/**
* Set of supported hdr values.
*
@ -488,7 +482,6 @@ public class CameraOptions {
return autoFocusSupported;
}
/**
* Whether exposure correction is supported. If this is false, calling
* {@link CameraView#setExposureCorrection(float)} has no effect.
@ -501,7 +494,6 @@ public class CameraOptions {
return exposureCorrectionSupported;
}
/**
* The minimum value of negative exposure correction, in EV stops.
* This is presumably negative or 0 if not supported.
@ -524,18 +516,20 @@ public class CameraOptions {
}
/**
* The minimum value for FPS
* The minimum value for the preview frame rate, in frames per second (FPS).
*
* @return the min value
*/
public float getFpsRangeMinValue() {
return fpsRangeMinValue;
public float getPreviewFrameRateMinValue() {
return previewFrameRateMinValue;
}
/**
* The maximum value for FPS
* The maximum value for the preview frame rate, in frames per second (FPS).
*
* @return the max value
*/
public float getFpsRangeMaxValue() {
return fpsRangeMaxValue;
public float getPreviewFrameRateMaxValue() {
return previewFrameRateMaxValue;
}
}

@ -1450,8 +1450,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
/**
* Sets the frame rate for the video
* Will be used by {@link #takeVideoSnapshot(File)}.
* Sets the preview frame rate in frames per second.
* This rate will be used, for example, by the frame processor and in video
* snapshot taken through {@link #takeVideo(File)}.
*
* A value of 0F will restore the rate to a default value.
*
* @param frameRate desired frame rate
*/
public void setPreviewFrameRate(float frameRate) {
@ -1459,7 +1463,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
}
/**
* Returns the current frame rate.
* Returns the current preview frame rate.
* This can return 0F if no frame rate was set.
*
* @see #setPreviewFrameRate(float)
* @return current frame rate
*/
public float getPreviewFrameRate() {

@ -12,8 +12,8 @@ import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import android.util.Range;
import android.view.SurfaceHolder;
import com.google.android.gms.tasks.Task;
@ -392,6 +392,7 @@ public class Camera1Engine extends CameraEngine implements
// The correct formula seems to be deviceOrientation+displayOffset,
// which means offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE).
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
stub.videoFrameRate = Math.round(mPreviewFrameRate);
LOG.i("onTakeVideoSnapshot", "rotation:", stub.rotation, "size:", stub.size);
// Start.
@ -423,6 +424,7 @@ public class Camera1Engine extends CameraEngine implements
applyZoom(params, 0F);
applyExposureCorrection(params, 0F);
applyPlaySounds(mPlaySounds);
applyPreviewFrameRate(params, 0F);
}
private void applyDefaultFocus(@NonNull Camera.Parameters params) {
@ -666,8 +668,53 @@ public class Camera1Engine extends CameraEngine implements
return false;
}
@Override public void setPreviewFrameRate(float previewFrameRate) {
// This method does nothing
@Override
public void setPreviewFrameRate(float previewFrameRate) {
final float old = previewFrameRate;
mPreviewFrameRate = previewFrameRate;
mHandler.run(new Runnable() {
@Override
public void run() {
if (getEngineState() == STATE_STARTED) {
Camera.Parameters params = mCamera.getParameters();
if (applyPreviewFrameRate(params, old)) mCamera.setParameters(params);
}
mPreviewFrameRateOp.end(null);
}
});
}
private boolean applyPreviewFrameRate(@NonNull Camera.Parameters params,
float oldPreviewFrameRate) {
List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
if (mPreviewFrameRate == 0F) {
// 0F is a special value. Fallback to a reasonable default.
for (int[] fpsRange : fpsRanges) {
float lower = (float) fpsRange[0] / 1000F;
float upper = (float) fpsRange[1] / 1000F;
if ((lower <= 30F && 30F <= upper) || (lower <= 24F && 24F <= upper)) {
params.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
return true;
}
}
} else {
// If out of boundaries, adjust it.
mPreviewFrameRate = Math.min(mPreviewFrameRate,
mCameraOptions.getPreviewFrameRateMaxValue());
mPreviewFrameRate = Math.max(mPreviewFrameRate,
mCameraOptions.getPreviewFrameRateMinValue());
for (int[] fpsRange : fpsRanges) {
float lower = (float) fpsRange[0] / 1000F;
float upper = (float) fpsRange[1] / 1000F;
float rate = Math.round(mPreviewFrameRate);
if (lower <= rate && rate <= upper) {
params.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
return true;
}
}
}
mPreviewFrameRate = oldPreviewFrameRate;
return false;
}
//endregion

@ -83,7 +83,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
private static final int FRAME_PROCESSING_FORMAT = ImageFormat.NV21;
private static final int FRAME_PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888;
private static final int DEFAULT_FRAME_RATE = 30;
@VisibleForTesting static final long METER_TIMEOUT = 2500;
private final CameraManager mManager;
@ -815,7 +814,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (!(mPreview instanceof GlCameraPreview)) {
throw new IllegalStateException("Video snapshots are only supported with GL_SURFACE.");
}
stub.videoFrameRate = (int) mPreviewFrameRate;
GlCameraPreview glPreview = (GlCameraPreview) mPreview;
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
if (outputSize == null) {
@ -834,6 +832,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// Unlike Camera1, the correct formula seems to be deviceOrientation,
// which means offset(Reference.BASE, Reference.OUTPUT, Axis.ABSOLUTE).
stub.rotation = getAngles().offset(Reference.BASE, Reference.OUTPUT, Axis.ABSOLUTE);
stub.videoFrameRate = Math.round(mPreviewFrameRate);
LOG.i("onTakeVideoSnapshot", "rotation:", stub.rotation, "size:", stub.size);
// Start.
@ -1273,25 +1272,34 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
}
@SuppressWarnings("WeakerAccess")
protected boolean applyPreviewFrameRate(@NonNull CaptureRequest.Builder builder, float oldPreviewFrameRate) {
Range<Integer>[] fpsRanges = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if (fpsRanges != null) {
if (mPreviewFrameRate != 0f) {
protected boolean applyPreviewFrameRate(@NonNull CaptureRequest.Builder builder,
float oldPreviewFrameRate) {
//noinspection unchecked
Range<Integer>[] fallback = new Range[]{};
Range<Integer>[] fpsRanges = readCharacteristic(
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
fallback);
if (mPreviewFrameRate == 0F) {
// 0F is a special value. Fallback to a reasonable default.
for (Range<Integer> fpsRange : fpsRanges) {
if (fpsRange.contains((int) mPreviewFrameRate)) {
if (fpsRange.contains(30) || fpsRange.contains(24)) {
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
return true;
}
}
} else {
// If out of boundaries, adjust it.
mPreviewFrameRate = Math.min(mPreviewFrameRate,
mCameraOptions.getPreviewFrameRateMaxValue());
mPreviewFrameRate = Math.max(mPreviewFrameRate,
mCameraOptions.getPreviewFrameRateMinValue());
for (Range<Integer> fpsRange : fpsRanges) {
if (fpsRange.contains(DEFAULT_FRAME_RATE)) {
if (fpsRange.contains(Math.round(mPreviewFrameRate))) {
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
return true;
}
}
}
}
mPreviewFrameRate = oldPreviewFrameRate;
return false;
}

@ -116,7 +116,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
new Option.Flash(), new Option.WhiteBalance(), new Option.Hdr(),
new Option.PictureMetering(), new Option.PictureSnapshotMetering(),
// Video recording
new Option.VideoCodec(), new Option.Audio(),
new Option.PreviewFrameRate(), new Option.VideoCodec(), new Option.Audio(),
// Gestures
new Option.Pinch(), new Option.HorizontalScroll(), new Option.VerticalScroll(),
new Option.Tap(), new Option.LongTap(),
@ -128,12 +128,19 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
new Option.Grid(), new Option.GridColor(), new Option.UseDeviceOrientation()
);
List<Boolean> dividers = Arrays.asList(
// Layout
false, true,
// Engine and preview
false, false, true,
// Some controls
false, false, false, false, true,
false, true,
// Video recording
false, false, true,
// Gestures
false, false, false, false, true,
// Watermarks
false, false, true,
// Other
false, false, true
);
for (int i = 0; i < options.size(); i++) {

@ -60,7 +60,7 @@ public abstract class Option<T> {
@Override
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
View root = (View) view.getParent();
ArrayList<Integer> list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
int boundary = root.getWidth();
if (boundary == 0) boundary = 1000;
int step = boundary / 10;
@ -506,4 +506,45 @@ public abstract class Option<T> {
view.setUseDeviceOrientation(value);
}
}
public static class PreviewFrameRate extends Option<Integer> {
public PreviewFrameRate() {
super("Preview FPS");
}
@NonNull
@Override
public Collection<Integer> getAll(@NonNull CameraView view, @NonNull CameraOptions options) {
float min = options.getPreviewFrameRateMinValue();
float max = options.getPreviewFrameRateMaxValue();
float delta = max - min;
List<Integer> result = new ArrayList<>();
if (min == 0F && max == 0F) {
return result; // empty list
} else if (delta < 0.005F) {
result.add(Math.round(min));
return result; // single value
} else {
final int steps = 3;
final float step = delta / steps;
for (int i = 0; i <= steps; i++) {
result.add(Math.round(min));
min += step;
}
return result;
}
}
@NonNull
@Override
public Integer get(@NonNull CameraView view) {
return Math.round(view.getPreviewFrameRate());
}
@Override
public void set(@NonNull CameraView view, @NonNull Integer value) {
view.setPreviewFrameRate((float) value);
}
}
}

@ -30,7 +30,8 @@ or `CameraOptions.supports(Control)` to see if it is supported.
app:cameraVideoCodec="deviceDefault|h263|h264"
app:cameraVideoMaxSize="0"
app:cameraVideoMaxDuration="0"
app:cameraVideoBitRate="0"/>
app:cameraVideoBitRate="0"
app:cameraPreviewFrameRate="30"/>
```
### APIs
@ -40,6 +41,8 @@ or `CameraOptions.supports(Control)` to see if it is supported.
Which camera to use, either back facing or front facing.
Defaults to the first available value (tries `BACK` first).
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setFacing(Facing.BACK);
cameraView.setFacing(Facing.FRONT);
@ -49,6 +52,8 @@ cameraView.setFacing(Facing.FRONT);
Flash mode, either off, on, auto or torch. Defaults to `OFF`.
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setFlash(Flash.OFF);
cameraView.setFlash(Flash.ON);
@ -61,6 +66,8 @@ cameraView.setFlash(Flash.TORCH);
Sets the encoder for video recordings. Defaults to `DEVICE_DEFAULT`,
which should typically be H_264.
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setVideoCodec(VideoCodec.DEVICE_DEFAULT);
cameraView.setVideoCodec(VideoCodec.H_263);
@ -72,6 +79,8 @@ cameraView.setVideoCodec(VideoCodec.H_264);
Sets the desired white balance for the current session.
Defaults to `AUTO`.
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setWhiteBalance(WhiteBalance.AUTO);
cameraView.setWhiteBalance(WhiteBalance.INCANDESCENT);
@ -84,6 +93,8 @@ cameraView.setWhiteBalance(WhiteBalance.CLOUDY);
Turns on or off HDR captures. Defaults to `OFF`.
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setHdr(Hdr.OFF);
cameraView.setHdr(Hdr.ON);
@ -94,6 +105,8 @@ cameraView.setHdr(Hdr.ON);
Turns on or off audio stream while recording videos.
Defaults to `ON`.
The available values are exposed through the `CameraOptions` object.
```java
cameraView.setAudio(Audio.OFF);
cameraView.setAudio(Audio.ON); // on but depends on video config
@ -143,6 +156,27 @@ cameraView.setVideoBitRate(0);
cameraView.setVideoBitRate(4000000);
```
##### cameraPreviewFrameRate
Controls the preview frame rate, in frames per second.
Use a value of 0F to restore the camera default value.
```java
cameraView.setPreviewFrameRate(30F);
cameraView.setPreviewFrameRate(0F);
```
The preview frame rate is an important parameter because it will also
control (broadly) the rate at which frame processor frames are dispatched,
the video snapshots frame rate, and the rate at which real-time filters are invoked.
The available values are exposed through the `CameraOptions` object:
```java
float min = options.getPreviewFrameRateMinValue();
float max = options.getPreviewFrameRateMaxValue();
```
### Zoom
There are two ways to control the zoom value:

Loading…
Cancel
Save