Skip to content

Commit 218aa9d

Browse files
authored
Preview FPS docs + Camera1 implementation (#661)
* Small changes * Camera1 options * Complete Camera1 integration * Add to demo app * Complete docs
1 parent fa88783 commit 218aa9d

File tree

9 files changed

+215
-62
lines changed

9 files changed

+215
-62
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Using CameraView is extremely simple:
122122
app:cameraGestureScrollVertical="none|zoom|exposureCorrection|filterControl1|filterControl2"
123123
app:cameraEngine="camera1|camera2"
124124
app:cameraPreview="glSurface|surface|texture"
125+
app:cameraPreviewFrameRate="@integer/preview_frame_rate"
125126
app:cameraFacing="back|front"
126127
app:cameraHdr="on|off"
127128
app:cameraFlash="on|auto|torch|off"

cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.Test;
2323
import org.junit.runner.RunWith;
2424

25+
import java.util.ArrayList;
2526
import java.util.Arrays;
2627
import java.util.Collection;
2728
import java.util.Collections;
@@ -345,4 +346,17 @@ public void testExposureCorrection() {
345346
assertEquals(o.getExposureCorrectionMaxValue(), 10f * 0.5f, 0f);
346347
}
347348

349+
@Test
350+
public void testPreviewFrameRate() {
351+
Camera.Parameters params = mock(Camera.Parameters.class);
352+
List<int[]> result = Arrays.asList(
353+
new int[]{20000, 30000},
354+
new int[]{30000, 60000},
355+
new int[]{60000, 120000}
356+
);
357+
when(params.getSupportedPreviewFpsRange()).thenReturn(result);
358+
CameraOptions o = new CameraOptions(params, 0, false);
359+
assertEquals(20F, o.getPreviewFrameRateMinValue(), 0.001F);
360+
assertEquals(120F, o.getPreviewFrameRateMaxValue(), 0.001F);
361+
}
348362
}

cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ public class CameraOptions {
6060
private float exposureCorrectionMinValue;
6161
private float exposureCorrectionMaxValue;
6262
private boolean autoFocusSupported;
63-
private float fpsRangeMinValue;
64-
private float fpsRangeMaxValue;
63+
private float previewFrameRateMinValue;
64+
private float previewFrameRateMaxValue;
6565

6666

6767
public CameraOptions(@NonNull Camera.Parameters params, int cameraId, boolean flipSizes) {
@@ -159,9 +159,16 @@ public CameraOptions(@NonNull Camera.Parameters params, int cameraId, boolean fl
159159
}
160160
}
161161

162-
//fps range
163-
fpsRangeMinValue = 0F;
164-
fpsRangeMaxValue = 0F;
162+
// Preview FPS
163+
previewFrameRateMinValue = Float.MAX_VALUE;
164+
previewFrameRateMaxValue = -Float.MAX_VALUE;
165+
List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
166+
for (int[] fpsRange : fpsRanges) {
167+
float lower = (float) fpsRange[0] / 1000F;
168+
float upper = (float) fpsRange[1] / 1000F;
169+
previewFrameRateMinValue = Math.min(previewFrameRateMinValue, lower);
170+
previewFrameRateMaxValue = Math.max(previewFrameRateMaxValue, upper);
171+
}
165172
}
166173

167174
// Camera2Engine constructor.
@@ -279,22 +286,19 @@ public CameraOptions(@NonNull CameraManager manager,
279286
}
280287
}
281288

282-
//fps Range
283-
fpsRangeMinValue = Float.MAX_VALUE;
284-
fpsRangeMaxValue = Float.MIN_VALUE;
285-
Range<Integer>[] range = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
289+
// Preview FPS
290+
Range<Integer>[] range = cameraCharacteristics.get(
291+
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
286292
if (range != null) {
293+
previewFrameRateMinValue = Float.MAX_VALUE;
294+
previewFrameRateMaxValue = -Float.MAX_VALUE;
287295
for (Range<Integer> fpsRange : range) {
288-
if (fpsRange.getLower() <= fpsRangeMinValue) {
289-
fpsRangeMinValue = fpsRange.getLower();
290-
}
291-
if (fpsRange.getUpper() >= fpsRangeMaxValue) {
292-
fpsRangeMaxValue = fpsRange.getUpper();
293-
}
296+
previewFrameRateMinValue = Math.min(previewFrameRateMinValue, fpsRange.getLower());
297+
previewFrameRateMaxValue = Math.max(previewFrameRateMaxValue, fpsRange.getUpper());
294298
}
295299
} else {
296-
fpsRangeMinValue = 0F;
297-
fpsRangeMaxValue = 0F;
300+
previewFrameRateMinValue = 0F;
301+
previewFrameRateMaxValue = 0F;
298302
}
299303
}
300304

@@ -308,7 +312,6 @@ public boolean supports(@NonNull Control control) {
308312
return getSupportedControls(control.getClass()).contains(control);
309313
}
310314

311-
312315
/**
313316
* Shorthand for other methods in this class,
314317
* e.g. supports(GestureAction.ZOOM) == isZoomSupported().
@@ -333,7 +336,6 @@ public boolean supports(@NonNull GestureAction action) {
333336
return false;
334337
}
335338

336-
337339
@SuppressWarnings("unchecked")
338340
@NonNull
339341
public <T extends Control> Collection<T> getSupportedControls(@NonNull Class<T> controlClass) {
@@ -362,7 +364,6 @@ public <T extends Control> Collection<T> getSupportedControls(@NonNull Class<T>
362364
return Collections.emptyList();
363365
}
364366

365-
366367
/**
367368
* Set of supported picture sizes for the currently opened camera.
368369
*
@@ -373,7 +374,6 @@ public Collection<Size> getSupportedPictureSizes() {
373374
return Collections.unmodifiableSet(supportedPictureSizes);
374375
}
375376

376-
377377
/**
378378
* Set of supported picture aspect ratios for the currently opened camera.
379379
*
@@ -385,7 +385,6 @@ public Collection<AspectRatio> getSupportedPictureAspectRatios() {
385385
return Collections.unmodifiableSet(supportedPictureAspectRatio);
386386
}
387387

388-
389388
/**
390389
* Set of supported video sizes for the currently opened camera.
391390
*
@@ -396,7 +395,6 @@ public Collection<Size> getSupportedVideoSizes() {
396395
return Collections.unmodifiableSet(supportedVideoSizes);
397396
}
398397

399-
400398
/**
401399
* Set of supported picture aspect ratios for the currently opened camera.
402400
*
@@ -408,7 +406,6 @@ public Collection<AspectRatio> getSupportedVideoAspectRatios() {
408406
return Collections.unmodifiableSet(supportedVideoAspectRatio);
409407
}
410408

411-
412409
/**
413410
* Set of supported facing values.
414411
*
@@ -421,7 +418,6 @@ public Collection<Facing> getSupportedFacing() {
421418
return Collections.unmodifiableSet(supportedFacing);
422419
}
423420

424-
425421
/**
426422
* Set of supported flash values.
427423
*
@@ -436,7 +432,6 @@ public Collection<Flash> getSupportedFlash() {
436432
return Collections.unmodifiableSet(supportedFlash);
437433
}
438434

439-
440435
/**
441436
* Set of supported white balance values.
442437
*
@@ -452,7 +447,6 @@ public Collection<WhiteBalance> getSupportedWhiteBalance() {
452447
return Collections.unmodifiableSet(supportedWhiteBalance);
453448
}
454449

455-
456450
/**
457451
* Set of supported hdr values.
458452
*
@@ -488,7 +482,6 @@ public boolean isAutoFocusSupported() {
488482
return autoFocusSupported;
489483
}
490484

491-
492485
/**
493486
* Whether exposure correction is supported. If this is false, calling
494487
* {@link CameraView#setExposureCorrection(float)} has no effect.
@@ -501,7 +494,6 @@ public boolean isExposureCorrectionSupported() {
501494
return exposureCorrectionSupported;
502495
}
503496

504-
505497
/**
506498
* The minimum value of negative exposure correction, in EV stops.
507499
* This is presumably negative or 0 if not supported.
@@ -524,18 +516,20 @@ public float getExposureCorrectionMaxValue() {
524516
}
525517

526518
/**
527-
* The minimum value for FPS
519+
* The minimum value for the preview frame rate, in frames per second (FPS).
520+
*
528521
* @return the min value
529522
*/
530-
public float getFpsRangeMinValue() {
531-
return fpsRangeMinValue;
523+
public float getPreviewFrameRateMinValue() {
524+
return previewFrameRateMinValue;
532525
}
533526

534527
/**
535-
* The maximum value for FPS
528+
* The maximum value for the preview frame rate, in frames per second (FPS).
529+
*
536530
* @return the max value
537531
*/
538-
public float getFpsRangeMaxValue() {
539-
return fpsRangeMaxValue;
532+
public float getPreviewFrameRateMaxValue() {
533+
return previewFrameRateMaxValue;
540534
}
541535
}

cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,16 +1450,23 @@ public int getVideoBitRate() {
14501450
}
14511451

14521452
/**
1453-
* Sets the frame rate for the video
1454-
* Will be used by {@link #takeVideoSnapshot(File)}.
1453+
* Sets the preview frame rate in frames per second.
1454+
* This rate will be used, for example, by the frame processor and in video
1455+
* snapshot taken through {@link #takeVideo(File)}.
1456+
*
1457+
* A value of 0F will restore the rate to a default value.
1458+
*
14551459
* @param frameRate desired frame rate
14561460
*/
14571461
public void setPreviewFrameRate(float frameRate) {
14581462
mCameraEngine.setPreviewFrameRate(frameRate);
14591463
}
14601464

14611465
/**
1462-
* Returns the current frame rate.
1466+
* Returns the current preview frame rate.
1467+
* This can return 0F if no frame rate was set.
1468+
*
1469+
* @see #setPreviewFrameRate(float)
14631470
* @return current frame rate
14641471
*/
14651472
public float getPreviewFrameRate() {

cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import androidx.annotation.NonNull;
1313
import androidx.annotation.Nullable;
1414
import androidx.annotation.VisibleForTesting;
15-
import androidx.annotation.WorkerThread;
1615

16+
import android.util.Range;
1717
import android.view.SurfaceHolder;
1818

1919
import com.google.android.gms.tasks.Task;
@@ -392,6 +392,7 @@ protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
392392
// The correct formula seems to be deviceOrientation+displayOffset,
393393
// which means offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE).
394394
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
395+
stub.videoFrameRate = Math.round(mPreviewFrameRate);
395396
LOG.i("onTakeVideoSnapshot", "rotation:", stub.rotation, "size:", stub.size);
396397

397398
// Start.
@@ -423,6 +424,7 @@ private void applyAllParameters(@NonNull Camera.Parameters params) {
423424
applyZoom(params, 0F);
424425
applyExposureCorrection(params, 0F);
425426
applyPlaySounds(mPlaySounds);
427+
applyPreviewFrameRate(params, 0F);
426428
}
427429

428430
private void applyDefaultFocus(@NonNull Camera.Parameters params) {
@@ -666,8 +668,53 @@ private boolean applyPlaySounds(boolean oldPlaySound) {
666668
return false;
667669
}
668670

669-
@Override public void setPreviewFrameRate(float previewFrameRate) {
670-
// This method does nothing
671+
@Override
672+
public void setPreviewFrameRate(float previewFrameRate) {
673+
final float old = previewFrameRate;
674+
mPreviewFrameRate = previewFrameRate;
675+
mHandler.run(new Runnable() {
676+
@Override
677+
public void run() {
678+
if (getEngineState() == STATE_STARTED) {
679+
Camera.Parameters params = mCamera.getParameters();
680+
if (applyPreviewFrameRate(params, old)) mCamera.setParameters(params);
681+
}
682+
mPreviewFrameRateOp.end(null);
683+
}
684+
});
685+
}
686+
687+
private boolean applyPreviewFrameRate(@NonNull Camera.Parameters params,
688+
float oldPreviewFrameRate) {
689+
List<int[]> fpsRanges = params.getSupportedPreviewFpsRange();
690+
if (mPreviewFrameRate == 0F) {
691+
// 0F is a special value. Fallback to a reasonable default.
692+
for (int[] fpsRange : fpsRanges) {
693+
float lower = (float) fpsRange[0] / 1000F;
694+
float upper = (float) fpsRange[1] / 1000F;
695+
if ((lower <= 30F && 30F <= upper) || (lower <= 24F && 24F <= upper)) {
696+
params.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
697+
return true;
698+
}
699+
}
700+
} else {
701+
// If out of boundaries, adjust it.
702+
mPreviewFrameRate = Math.min(mPreviewFrameRate,
703+
mCameraOptions.getPreviewFrameRateMaxValue());
704+
mPreviewFrameRate = Math.max(mPreviewFrameRate,
705+
mCameraOptions.getPreviewFrameRateMinValue());
706+
for (int[] fpsRange : fpsRanges) {
707+
float lower = (float) fpsRange[0] / 1000F;
708+
float upper = (float) fpsRange[1] / 1000F;
709+
float rate = Math.round(mPreviewFrameRate);
710+
if (lower <= rate && rate <= upper) {
711+
params.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
712+
return true;
713+
}
714+
}
715+
}
716+
mPreviewFrameRate = oldPreviewFrameRate;
717+
return false;
671718
}
672719

673720
//endregion

cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
8383

8484
private static final int FRAME_PROCESSING_FORMAT = ImageFormat.NV21;
8585
private static final int FRAME_PROCESSING_INPUT_FORMAT = ImageFormat.YUV_420_888;
86-
private static final int DEFAULT_FRAME_RATE = 30;
8786
@VisibleForTesting static final long METER_TIMEOUT = 2500;
8887

8988
private final CameraManager mManager;
@@ -815,7 +814,6 @@ protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
815814
if (!(mPreview instanceof GlCameraPreview)) {
816815
throw new IllegalStateException("Video snapshots are only supported with GL_SURFACE.");
817816
}
818-
stub.videoFrameRate = (int) mPreviewFrameRate;
819817
GlCameraPreview glPreview = (GlCameraPreview) mPreview;
820818
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT);
821819
if (outputSize == null) {
@@ -834,6 +832,7 @@ protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub,
834832
// Unlike Camera1, the correct formula seems to be deviceOrientation,
835833
// which means offset(Reference.BASE, Reference.OUTPUT, Axis.ABSOLUTE).
836834
stub.rotation = getAngles().offset(Reference.BASE, Reference.OUTPUT, Axis.ABSOLUTE);
835+
stub.videoFrameRate = Math.round(mPreviewFrameRate);
837836
LOG.i("onTakeVideoSnapshot", "rotation:", stub.rotation, "size:", stub.size);
838837

839838
// Start.
@@ -1273,22 +1272,31 @@ public void run() {
12731272
}
12741273

12751274
@SuppressWarnings("WeakerAccess")
1276-
protected boolean applyPreviewFrameRate(@NonNull CaptureRequest.Builder builder, float oldPreviewFrameRate) {
1277-
Range<Integer>[] fpsRanges = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
1278-
if (fpsRanges != null) {
1279-
if (mPreviewFrameRate != 0f) {
1280-
for (Range<Integer> fpsRange : fpsRanges) {
1281-
if (fpsRange.contains((int) mPreviewFrameRate)) {
1282-
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
1283-
return true;
1284-
}
1275+
protected boolean applyPreviewFrameRate(@NonNull CaptureRequest.Builder builder,
1276+
float oldPreviewFrameRate) {
1277+
//noinspection unchecked
1278+
Range<Integer>[] fallback = new Range[]{};
1279+
Range<Integer>[] fpsRanges = readCharacteristic(
1280+
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
1281+
fallback);
1282+
if (mPreviewFrameRate == 0F) {
1283+
// 0F is a special value. Fallback to a reasonable default.
1284+
for (Range<Integer> fpsRange : fpsRanges) {
1285+
if (fpsRange.contains(30) || fpsRange.contains(24)) {
1286+
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
1287+
return true;
12851288
}
1286-
} else {
1287-
for (Range<Integer> fpsRange : fpsRanges) {
1288-
if (fpsRange.contains(DEFAULT_FRAME_RATE)) {
1289-
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
1290-
return true;
1291-
}
1289+
}
1290+
} else {
1291+
// If out of boundaries, adjust it.
1292+
mPreviewFrameRate = Math.min(mPreviewFrameRate,
1293+
mCameraOptions.getPreviewFrameRateMaxValue());
1294+
mPreviewFrameRate = Math.max(mPreviewFrameRate,
1295+
mCameraOptions.getPreviewFrameRateMinValue());
1296+
for (Range<Integer> fpsRange : fpsRanges) {
1297+
if (fpsRange.contains(Math.round(mPreviewFrameRate))) {
1298+
builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
1299+
return true;
12921300
}
12931301
}
12941302
}

0 commit comments

Comments
 (0)