Skip to content

Commit e1721bb

Browse files
authored
Frame Processing maxWidth, maxHeight and format (#704)
* Create CameraEngine and CameraBaseEngine * Promote filters to stable - no experimental flag * Fix setSnapshotMaxWidth / Height bugs * Add setFrameProcessingMaxWidth and setFrameProcessingMaxHeight * Add setFrameProcessingMaxWidth and setFrameProcessingMaxHeight (docs) * Prepare Frame for Images, abstract FrameManager, create ByteBufferFrameManager * Fix tests * Fix unit tests * Send Images for Camera2 * Tests * Add CameraView.setFrameProcessingFormat(int), tests, docs * Add CameraOptions.getSupportedFrameProcessingFormats(), tests * Add CameraEngine support, integration tests * Fix demo app, add getFrameProcessingPoolSize * Fix tests * Fix tests
1 parent 4a6b9be commit e1721bb

30 files changed

Lines changed: 2054 additions & 1404 deletions

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
with:
6565
java-version: 1.8
6666
- name: Execute emulator tests
67-
timeout-minutes: 20
67+
timeout-minutes: 30
6868
uses: reactivecircus/android-emulator-runner@v2.2.0
6969
with:
7070
api-level: ${{ matrix.EMULATOR_API }}

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ Using CameraView is extremely simple:
114114
app:cameraVideoSizeAspectRatio="@string/video_ratio"
115115
app:cameraSnapshotMaxWidth="@integer/snapshot_max_width"
116116
app:cameraSnapshotMaxHeight="@integer/snapshot_max_height"
117+
app:cameraFrameProcessingMaxWidth="@integer/processing_max_width"
118+
app:cameraFrameProcessingMaxHeight="@integer/processing_max_height"
119+
app:cameraFrameProcessingFormat="@integer/processing_format"
117120
app:cameraVideoBitRate="@integer/video_bit_rate"
118121
app:cameraAudioBitRate="@integer/audio_bit_rate"
119122
app:cameraGestureTap="none|autoFocus|takePicture"

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

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import android.content.Context;
55
import android.content.res.TypedArray;
6+
import android.graphics.ImageFormat;
67
import android.graphics.PointF;
78
import android.location.Location;
89
import androidx.annotation.NonNull;
@@ -168,8 +169,13 @@ public void testDefaults() {
168169
assertEquals(cameraView.getLocation(), null);
169170
assertEquals(cameraView.getExposureCorrection(), 0f, 0f);
170171
assertEquals(cameraView.getZoom(), 0f, 0f);
171-
assertEquals(cameraView.getVideoMaxDuration(), 0, 0);
172-
assertEquals(cameraView.getVideoMaxSize(), 0, 0);
172+
assertEquals(cameraView.getVideoMaxDuration(), 0);
173+
assertEquals(cameraView.getVideoMaxSize(), 0);
174+
assertEquals(cameraView.getSnapshotMaxWidth(), 0);
175+
assertEquals(cameraView.getSnapshotMaxHeight(), 0);
176+
assertEquals(cameraView.getFrameProcessingMaxWidth(), 0);
177+
assertEquals(cameraView.getFrameProcessingMaxHeight(), 0);
178+
assertEquals(cameraView.getFrameProcessingFormat(), 0);
173179

174180
// Self managed
175181
GestureParser gestures = new GestureParser(empty);
@@ -801,6 +807,30 @@ public void testPreviewFrameRate() {
801807
assertEquals(cameraView.getPreviewFrameRate(), 60, 0);
802808
}
803809

810+
@Test
811+
public void testSnapshotMaxSize() {
812+
cameraView.setSnapshotMaxWidth(500);
813+
assertEquals(500, cameraView.getSnapshotMaxWidth());
814+
cameraView.setSnapshotMaxHeight(700);
815+
assertEquals(700, cameraView.getSnapshotMaxHeight());
816+
}
817+
818+
@Test
819+
public void testFrameProcessingMaxSize() {
820+
cameraView.setFrameProcessingMaxWidth(500);
821+
assertEquals(500, cameraView.getFrameProcessingMaxWidth());
822+
cameraView.setFrameProcessingMaxHeight(700);
823+
assertEquals(700, cameraView.getFrameProcessingMaxHeight());
824+
}
825+
826+
@Test
827+
public void testFrameProcessingFormat() {
828+
cameraView.setFrameProcessingFormat(ImageFormat.YUV_420_888);
829+
assertEquals(ImageFormat.YUV_420_888, cameraView.getFrameProcessingFormat());
830+
cameraView.setFrameProcessingFormat(ImageFormat.YUV_422_888);
831+
assertEquals(ImageFormat.YUV_422_888, cameraView.getFrameProcessingFormat());
832+
}
833+
804834
//endregion
805835

806836
//region Lists of listeners and processors
@@ -975,31 +1005,18 @@ public void testOverlays_dontRemoveOverlayView() {
9751005
}
9761006

9771007
//endregion
978-
// TODO: test permissions
9791008

9801009
//region Filter
9811010

982-
@Test(expected = RuntimeException.class)
983-
public void testSetFilter_notExperimental() {
984-
cameraView.setExperimental(false);
985-
cameraView.setFilter(Filters.AUTO_FIX.newInstance());
986-
}
987-
988-
@Test
989-
public void testSetFilter_notExperimental_noFilter() {
990-
cameraView.setExperimental(false);
991-
cameraView.setFilter(Filters.NONE.newInstance());
992-
// no exception thrown
993-
}
994-
9951011
@Test
9961012
public void testSetFilter() {
997-
cameraView.setExperimental(true);
9981013
Filter filter = Filters.AUTO_FIX.newInstance();
9991014
cameraView.setFilter(filter);
10001015
verify(mockPreview, times(1)).setFilter(filter);
10011016
assertEquals(filter, cameraView.getFilter());
10021017
//noinspection ResultOfMethodCallIgnored
10031018
verify(mockPreview, times(1)).getCurrentFilter();
10041019
}
1020+
1021+
//endregion
10051022
}

cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera1IntegrationTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package com.otaliastudios.cameraview.engine;
22

3+
import com.otaliastudios.cameraview.CameraLogger;
4+
import com.otaliastudios.cameraview.CameraOptions;
35
import com.otaliastudios.cameraview.controls.Engine;
6+
import com.otaliastudios.cameraview.frame.Frame;
7+
import com.otaliastudios.cameraview.frame.FrameProcessor;
8+
import com.otaliastudios.cameraview.tools.Op;
9+
import com.otaliastudios.cameraview.tools.Retry;
10+
import com.otaliastudios.cameraview.tools.SdkExclude;
411

512
import org.junit.Test;
613
import org.junit.runner.RunWith;
@@ -10,6 +17,10 @@
1017
import androidx.test.filters.LargeTest;
1118
import androidx.test.filters.RequiresDevice;
1219

20+
import java.util.Collection;
21+
22+
import static org.junit.Assert.assertNotNull;
23+
1324
/**
1425
* These tests work great on real devices, and are the only way to test actual CameraEngine
1526
* implementation - we really need to open the camera device.
@@ -31,4 +42,10 @@ protected Engine getEngine() {
3142
protected long getMeteringTimeoutMillis() {
3243
return Camera1Engine.AUTOFOCUS_END_DELAY_MILLIS;
3344
}
45+
46+
@Override
47+
public void testFrameProcessing_maxSize() {
48+
// Camera1Engine does not support different sizes.
49+
// super.testFrameProcessing_maxSize();
50+
}
3451
}

cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/Camera2IntegrationTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.otaliastudios.cameraview.engine.action.ActionHolder;
1010
import com.otaliastudios.cameraview.engine.action.BaseAction;
1111

12+
import org.junit.Test;
1213
import org.junit.runner.RunWith;
1314

1415
import androidx.annotation.NonNull;
@@ -76,4 +77,10 @@ protected boolean canSetVideoMaxDuration() {
7677
if (shouldOpen) closeSync(true);
7778
return result;
7879
}
80+
81+
@Override
82+
public void testFrameProcessing_freezeRelease() {
83+
// Camera2 Frames are not freezable.
84+
// super.testFrameProcessing_freezeRelease();
85+
}
7986
}

cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.mockito.ArgumentMatcher;
5252

5353
import java.io.File;
54+
import java.util.Collection;
5455
import java.util.concurrent.CountDownLatch;
5556
import java.util.concurrent.TimeUnit;
5657

@@ -70,7 +71,7 @@
7071
import static org.mockito.Mockito.verify;
7172
import static org.mockito.Mockito.when;
7273

73-
public abstract class CameraIntegrationTest<E extends CameraEngine> extends BaseTest {
74+
public abstract class CameraIntegrationTest<E extends CameraBaseEngine> extends BaseTest {
7475

7576
private final static CameraLogger LOG = CameraLogger.create(CameraIntegrationTest.class.getSimpleName());
7677
private final static long DELAY = 8000;
@@ -1043,6 +1044,27 @@ public void testFrameProcessing_simple() throws Exception {
10431044
assert15Frames(processor);
10441045
}
10451046

1047+
@Test
1048+
@Retry(emulatorOnly = true)
1049+
@SdkExclude(maxSdkVersion = 22, emulatorOnly = true)
1050+
public void testFrameProcessing_maxSize() {
1051+
final int max = 600;
1052+
camera.setFrameProcessingMaxWidth(max);
1053+
camera.setFrameProcessingMaxHeight(max);
1054+
final Op<Size> sizeOp = new Op<>();
1055+
camera.addFrameProcessor(new FrameProcessor() {
1056+
@Override
1057+
public void process(@NonNull Frame frame) {
1058+
sizeOp.controller().end(frame.getSize());
1059+
}
1060+
});
1061+
openSync(true);
1062+
Size size = sizeOp.await(2000);
1063+
assertNotNull(size);
1064+
assertTrue(size.getWidth() <= max);
1065+
assertTrue(size.getHeight() <= max);
1066+
}
1067+
10461068
@Test
10471069
@Retry(emulatorOnly = true)
10481070
@SdkExclude(maxSdkVersion = 22, emulatorOnly = true)
@@ -1109,6 +1131,35 @@ public void process(@NonNull Frame frame) {
11091131
}
11101132
}
11111133

1134+
@Test
1135+
@Retry(emulatorOnly = true)
1136+
@SdkExclude(maxSdkVersion = 22, emulatorOnly = true)
1137+
public void testFrameProcessing_format() {
1138+
CameraOptions o = openSync(true);
1139+
Collection<Integer> formats = o.getSupportedFrameProcessingFormats();
1140+
for (int format : formats) {
1141+
LOG.i("[TEST FRAME FORMAT]", "Testing", format, "...");
1142+
Op<Boolean> op = testFrameProcessorFormat(format);
1143+
assertNotNull(op.await(DELAY));
1144+
}
1145+
}
1146+
1147+
@NonNull
1148+
private Op<Boolean> testFrameProcessorFormat(final int format) {
1149+
final Op<Boolean> op = new Op<>();
1150+
camera.setFrameProcessingFormat(format);
1151+
camera.addFrameProcessor(new FrameProcessor() {
1152+
@Override
1153+
public void process(@NonNull Frame frame) {
1154+
if (frame.getFormat() == format) {
1155+
op.controller().start();
1156+
op.controller().end(true);
1157+
}
1158+
}
1159+
});
1160+
return op;
1161+
}
1162+
11121163
//endregion
11131164

11141165
//region Overlays

cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.otaliastudios.cameraview.controls.Flash;
1414
import com.otaliastudios.cameraview.controls.PictureFormat;
1515
import com.otaliastudios.cameraview.engine.orchestrator.CameraState;
16+
import com.otaliastudios.cameraview.frame.ByteBufferFrameManager;
1617
import com.otaliastudios.cameraview.frame.FrameManager;
1718
import com.otaliastudios.cameraview.gesture.Gesture;
1819
import com.otaliastudios.cameraview.controls.Hdr;
@@ -27,7 +28,7 @@
2728
import java.util.List;
2829
import java.util.concurrent.Callable;
2930

30-
public class MockCameraEngine extends CameraEngine {
31+
public class MockCameraEngine extends CameraBaseEngine {
3132

3233
public boolean mPictureCaptured;
3334
public boolean mFocusStarted;
@@ -83,7 +84,7 @@ public void setMockPreviewStreamSize(Size size) {
8384
}
8485

8586
public void setMockState(@NonNull CameraState state) {
86-
Task<Void> change = mOrchestrator.scheduleStateChange(getState(),
87+
Task<Void> change = getOrchestrator().scheduleStateChange(getState(),
8788
state,
8889
false,
8990
new Callable<Task<Void>>() {
@@ -109,7 +110,6 @@ public void setExposureCorrection(float EVvalue, @NonNull float[] bounds, @Nulla
109110
mExposureCorrectionChanged = true;
110111
}
111112

112-
113113
@Override
114114
public void setFlash(@NonNull Flash flash) {
115115
mFlash = flash;
@@ -135,6 +135,16 @@ public void setPictureFormat(@NonNull PictureFormat pictureFormat) {
135135
mPictureFormat = pictureFormat;
136136
}
137137

138+
@Override
139+
public void setHasFrameProcessors(boolean hasFrameProcessors) {
140+
mHasFrameProcessors = hasFrameProcessors;
141+
}
142+
143+
@Override
144+
public void setFrameProcessingFormat(int format) {
145+
mFrameProcessingFormat = format;
146+
}
147+
138148
@Override
139149
public void takePicture(@NonNull PictureResult.Stub stub) {
140150
super.takePicture(stub);
@@ -172,6 +182,12 @@ protected List<Size> getPreviewStreamAvailableSizes() {
172182
return new ArrayList<>();
173183
}
174184

185+
@NonNull
186+
@Override
187+
protected List<Size> getFrameProcessingAvailableSizes() {
188+
return new ArrayList<>();
189+
}
190+
175191
@Override
176192
public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) {
177193
mFocusStarted = true;
@@ -180,13 +196,11 @@ public void startAutoFocus(@Nullable Gesture gesture, @NonNull PointF point) {
180196
@NonNull
181197
@Override
182198
protected FrameManager instantiateFrameManager() {
183-
return new FrameManager(2, null);
199+
return new ByteBufferFrameManager(2, null);
184200
}
185201

186202
@Override
187-
public void setPlaySounds(boolean playSounds) {
188-
189-
}
203+
public void setPlaySounds(boolean playSounds) { }
190204

191205
@Override
192206
protected boolean collectCameraInfo(@NonNull Facing facing) {

cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/options/Camera1OptionsTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.otaliastudios.cameraview.engine.options;
22

33

4+
import android.graphics.ImageFormat;
45
import android.hardware.Camera;
56

67
import com.otaliastudios.cameraview.BaseTest;
78
import com.otaliastudios.cameraview.CameraOptions;
89
import com.otaliastudios.cameraview.controls.Audio;
910
import com.otaliastudios.cameraview.controls.Facing;
1011
import com.otaliastudios.cameraview.controls.Flash;
12+
import com.otaliastudios.cameraview.controls.PictureFormat;
1113
import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper;
1214
import com.otaliastudios.cameraview.gesture.GestureAction;
1315
import com.otaliastudios.cameraview.controls.Grid;
@@ -54,6 +56,11 @@ public void testEmpty() {
5456
assertFalse(o.isZoomSupported());
5557
assertEquals(o.getExposureCorrectionMaxValue(), 0f, 0);
5658
assertEquals(o.getExposureCorrectionMinValue(), 0f, 0);
59+
// Static
60+
assertEquals(1, o.getSupportedPictureFormats().size());
61+
assertTrue(o.getSupportedPictureFormats().contains(PictureFormat.JPEG));
62+
assertEquals(1, o.getSupportedFrameProcessingFormats().size());
63+
assertTrue(o.getSupportedFrameProcessingFormats().contains(ImageFormat.NV21));
5764
}
5865

5966
private Camera.Size mockCameraSize(int width, int height) {

0 commit comments

Comments
 (0)