pull/572/head
Mattia Iavarone 6 years ago
parent 6f4ef00754
commit 01d7109ee1
  1. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegration2Test.java
  2. 26
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  3. 18
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/frame/FrameManagerTest.java
  4. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/gesture/ScrollGestureFinderTest.java
  5. 28
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  6. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/frame/Frame.java
  7. 45
      cameraview/src/main/java/com/otaliastudios/cameraview/frame/FrameManager.java
  8. 4
      cameraview/src/test/java/com/otaliastudios/cameraview/frame/FrameTest.java

@ -27,4 +27,9 @@ public class CameraIntegration2Test extends CameraIntegrationTest {
protected Engine getEngine() {
return Engine.CAMERA2;
}
@Override
public void testFrameProcessing_afterVideo() throws Exception {
super.testFrameProcessing_afterVideo();
}
}

@ -136,6 +136,7 @@ public abstract class CameraIntegrationTest extends BaseTest {
}
}
@SuppressWarnings("StatementWithEmptyBody")
private CameraOptions openSync(boolean expectSuccess) {
camera.open();
final Op<CameraOptions> open = new Op<>(true);
@ -143,10 +144,11 @@ public abstract class CameraIntegrationTest extends BaseTest {
CameraOptions result = open.await(DELAY);
if (expectSuccess) {
assertNotNull("Can open", result);
// Extra wait for the bind state.
// TODO fix this and other while {} in this class in a more elegant way.
//noinspection StatementWithEmptyBody
// Extra wait for the bind and preview state, so we run tests in a fully operational
// state. If we didn't do so, we could have null values, for example, in getPictureSize
// or in getSnapshotSize.
while (controller.getBindState() != CameraEngine.STATE_STARTED) {}
while (controller.getPreviewState() != CameraEngine.STATE_STARTED) {}
} else {
assertNull("Should not open", result);
}
@ -194,6 +196,9 @@ public abstract class CameraIntegrationTest extends BaseTest {
video.listen();
result = video.await(DELAY);
}
// Sleep another 1000, because camera.isTakingVideo() might return false even
// if the result still has to be dispatched. Rare but could happen.
try { Thread.sleep(1000); } catch (InterruptedException ignore) {}
}
// Now we should be OK.
@ -684,14 +689,11 @@ public abstract class CameraIntegrationTest extends BaseTest {
assertEquals(1, latch.getCount());
}
@SuppressWarnings("StatementWithEmptyBody")
@Test
public void testCapturePicture_size() {
openSync(true);
// PictureSize can still be null after opened.
// TODO be more elegant
while (camera.getPictureSize() == null) {}
Size size = camera.getPictureSize();
assertNotNull(size);
camera.takePicture();
PictureResult result = waitForPictureResult(true);
assertNotNull(result);
@ -734,14 +736,11 @@ public abstract class CameraIntegrationTest extends BaseTest {
assertEquals(1, latch.getCount());
}
@SuppressWarnings("StatementWithEmptyBody")
@Test
public void testCaptureSnapshot_size() {
openSync(true);
// SnapshotSize can still be null after opened.
// TODO be more elegant
while (camera.getSnapshotSize() == null) {}
Size size = camera.getSnapshotSize();
assertNotNull(size);
camera.takePictureSnapshot();
PictureResult result = waitForPictureResult(true);
@ -764,8 +763,8 @@ public abstract class CameraIntegrationTest extends BaseTest {
// Expect 30 frames
CountDownLatch latch = new CountDownLatch(30);
doCountDown(latch).when(mock).process(any(Frame.class));
boolean did = latch.await(60, TimeUnit.SECONDS);
assertTrue(did);
boolean did = latch.await(15, TimeUnit.SECONDS);
assertTrue("Latch count should be 0: " + latch.getCount(), did);
}
@Test
@ -811,6 +810,7 @@ public abstract class CameraIntegrationTest extends BaseTest {
openSync(true);
takeVideoSync(true,4000);
waitForVideoResult(true);
assert30Frames(processor);
}

@ -3,14 +3,18 @@ package com.otaliastudios.cameraview.frame;
import android.graphics.ImageFormat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.size.Size;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@ -18,7 +22,9 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class FrameManagerTest {
@RunWith(AndroidJUnit4.class)
@SmallTest
public class FrameManagerTest extends BaseTest {
private FrameManager.BufferCallback callback;
@ -68,11 +74,11 @@ public class FrameManagerTest {
// Since frame1 is already taken and poolSize = 1, a new Frame is created.
Frame frame2 = manager.getFrame(new byte[length], 0, 0);
// Release the first frame so it goes back into the pool.
manager.onFrameReleased(frame1);
manager.onFrameReleased(frame1, frame1.getData());
reset(callback);
// Release the second. The pool is already full, so onBufferAvailable should not be called
// since this Frame instance will NOT be reused.
manager.onFrameReleased(frame2);
manager.onFrameReleased(frame2, frame2.getData());
verify(callback, never()).onBufferAvailable(frame2.getData());
}
@ -87,7 +93,7 @@ public class FrameManagerTest {
// Release the frame and ensure that onBufferAvailable is called.
reset(callback);
manager.onFrameReleased(frame);
manager.onFrameReleased(frame, frame.getData());
verify(callback, times(1)).onBufferAvailable(picture);
}
@ -105,7 +111,7 @@ public class FrameManagerTest {
// Now release the old frame and ensure that onBufferAvailable is NOT called,
// because the released data has wrong length.
manager.onFrameReleased(frame);
manager.onFrameReleased(frame, frame.getData());
reset(callback);
verify(callback, never()).onBufferAvailable(picture);
}

@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue;
@SmallTest
public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFinder> {
private final static long WAIT = 2000; // 500 was too short
@Override
protected ScrollGestureFinder createFinder(@NonNull GestureFinder.Controller controller) {
return new ScrollGestureFinder(controller);
@ -42,7 +44,7 @@ public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFind
touchOp.listen();
touchOp.start();
onLayout().perform(swipeUp());
Gesture found = touchOp.await(500);
Gesture found = touchOp.await(WAIT);
assertNull(found);
}
@ -50,7 +52,7 @@ public class ScrollGestureFinderTest extends GestureFinderTest<ScrollGestureFind
touchOp.listen();
touchOp.start();
onLayout().perform(scroll);
Gesture found = touchOp.await(500);
Gesture found = touchOp.await(WAIT);
assertEquals(found, expected);
// How will this move our parameter?

@ -727,12 +727,30 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override
protected void onStopVideo() {
// When video ends, we have to restart the repeating request for TEMPLATE_PREVIEW,
// this time without the video recorder surface. We do this before stopping the
// recorder. If we stop first, the camera will try to fill an "abandoned" Surface
// and, on some devices with a poor internal implementation, this crashes. See #549
boolean isFullVideo = mVideoRecorder instanceof Full2VideoRecorder;
if (isFullVideo) {
// Workaround for #549: when video ends we must stop the recorder and remove the recorder
// surface from camera outputs. On some devices, order matters. If we stop the recorder
// and AFTER send camera frames to it, the camera will try to fill the recorder "abandoned"
// Surface and on some devices with a poor internal implementation (LEGACY?) this crashes.
try {
mSession.stopRepeating();
mSession.abortCaptures();
} catch (CameraAccessException e) {
throw createCameraException(e);
}
}
super.onStopVideo();
}
@Override
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {
boolean isFullVideo = mVideoRecorder instanceof Full2VideoRecorder;
super.onVideoResult(result, exception);
if (isFullVideo) {
// When video ends, we must restart the repeating request for TEMPLATE_PREVIEW,
// this time without the video recorder surface.
try {
createRepeatingRequestBuilder(CameraDevice.TEMPLATE_PREVIEW);
addRepeatingRequestBuilderSurfaces();
@ -741,7 +759,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
throw createCameraException(e);
}
}
super.onStopVideo();
}
//endregion

@ -80,12 +80,16 @@ public class Frame {
public void release() {
if (!hasContent()) return;
LOG.v("Frame with time", mTime, "is being released.");
mManager.onFrameReleased(this);
byte[] data = mData;
mData = null;
mRotation = 0;
mTime = -1;
mSize = null;
mFormat = -1;
// After the manager is notified, this frame instance can be taken by
// someone else, possibly from another thread. So this should be the
// last call in this method. If we null data after, we can have issues.
mManager.onFrameReleased(this, data);
}
/**

@ -202,27 +202,6 @@ public class FrameManager {
return frame;
}
/**
* Releases all frames controlled by this manager and
* clears the pool.
* In BUFFER_MODE_ENQUEUE, releases also all the buffers.
*/
public void release() {
if (!isSetUp()) {
LOG.w("release called twice. Ignoring.");
return;
}
LOG.i("release: Clearing the frame and buffer queue.");
mFrameQueue.clear();
if (mBufferMode == BUFFER_MODE_ENQUEUE) {
mBufferQueue.clear();
}
mBufferSize = -1;
mFrameSize = null;
mFrameFormat = -1;
}
/**
* Called by child frames when they are released.
* This might be called from old Frames that belong to an old 'setUp'
@ -231,12 +210,11 @@ public class FrameManager {
*
* @param frame the released frame
*/
void onFrameReleased(@NonNull Frame frame) {
void onFrameReleased(@NonNull Frame frame, @NonNull byte[] buffer) {
if (!isSetUp()) return;
// If frame queue is full, let's drop everything.
// If frame queue accepts this frame, let's recycle the buffer as well.
if (mFrameQueue.offer(frame)) {
byte[] buffer = frame.getData();
int currSize = buffer.length;
int reqSize = mBufferSize;
if (currSize == reqSize) {
@ -248,4 +226,25 @@ public class FrameManager {
}
}
}
/**
* Releases all frames controlled by this manager and
* clears the pool.
* In BUFFER_MODE_ENQUEUE, releases also all the buffers.
*/
public void release() {
if (!isSetUp()) {
LOG.w("release called twice. Ignoring.");
return;
}
LOG.i("release: Clearing the frame and buffer queue.");
mFrameQueue.clear();
if (mBufferMode == BUFFER_MODE_ENQUEUE) {
mBufferQueue.clear();
}
mBufferSize = -1;
mFrameSize = null;
mFrameFormat = -1;
}
}

@ -15,6 +15,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -52,7 +54,7 @@ public class FrameTest {
final Frame frame = new Frame(manager);
frame.setContent(new byte[2], 1000, 90, new Size(10, 10), ImageFormat.NV21);
frame.release();
verify(manager, times(1)).onFrameReleased(frame);
verify(manager, times(1)).onFrameReleased(eq(frame), any(byte[].class));
assertThrows(new Runnable() { public void run() { frame.getTime(); }});
assertThrows(new Runnable() { public void run() { frame.getFormat(); }});

Loading…
Cancel
Save