Frame processors (#82)
* Create the Frame class * Implement callback dispatcher * Dispatch actual preview frames * Add docs * Readme nits * Don't leak processors * Rename clear() to release() * Add preview format and Size * Readme nits againpull/83/head
parent
399e0e4d76
commit
e40f93acfb
@ -0,0 +1,106 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
/** |
||||
* A preview frame to be processed by {@link FrameProcessor}s. |
||||
*/ |
||||
public class Frame { |
||||
|
||||
private byte[] mData = null; |
||||
private long mTime = -1; |
||||
private int mRotation = 0; |
||||
private Size mSize = null; |
||||
private int mFormat = -1; |
||||
|
||||
Frame() {} |
||||
|
||||
void set(byte[] data, long time, int rotation, Size size, int format) { |
||||
this.mData = data; |
||||
this.mTime = time; |
||||
this.mRotation = rotation; |
||||
this.mSize = size; |
||||
this.mFormat = format; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object obj) { |
||||
// We want a super fast implementation here, do not compare arrays.
|
||||
return obj instanceof Frame && ((Frame) obj).mTime == mTime; |
||||
} |
||||
|
||||
/** |
||||
* Clones the frame, returning a frozen content that will not be overwritten. |
||||
* This can be kept or safely passed to other threads. |
||||
* Using freeze without clearing with {@link #release()} can result in memory leaks. |
||||
* |
||||
* @return a frozen Frame |
||||
*/ |
||||
public Frame freeze() { |
||||
byte[] data = new byte[mData.length]; |
||||
System.arraycopy(mData, 0, data, 0, mData.length); |
||||
Frame other = new Frame(); |
||||
other.set(data, mTime, mRotation, mSize, mFormat); |
||||
return other; |
||||
} |
||||
|
||||
/** |
||||
* Disposes the contents of this frame. Can be useful for frozen frames |
||||
* that are not useful anymore. |
||||
*/ |
||||
public void release() { |
||||
mData = null; |
||||
mRotation = 0; |
||||
mTime = -1; |
||||
mSize = null; |
||||
mFormat = -1; |
||||
} |
||||
|
||||
/** |
||||
* Returns the frame data. |
||||
* @return the frame data |
||||
*/ |
||||
public byte[] getData() { |
||||
return mData; |
||||
} |
||||
|
||||
/** |
||||
* Returns the milliseconds epoch for this frame, |
||||
* in the {@link System#currentTimeMillis()} reference. |
||||
* |
||||
* @return time data |
||||
*/ |
||||
public long getTime() { |
||||
return mTime; |
||||
} |
||||
|
||||
/** |
||||
* Returns the clock-wise rotation that should be applied on the data |
||||
* array, such that the resulting frame matches what the user is seeing |
||||
* on screen. |
||||
* |
||||
* @return clock-wise rotation |
||||
*/ |
||||
public int getRotation() { |
||||
return mRotation; |
||||
} |
||||
|
||||
/** |
||||
* Returns the frame size. |
||||
* |
||||
* @return frame size |
||||
*/ |
||||
public Size getSize() { |
||||
return mSize; |
||||
} |
||||
|
||||
/** |
||||
* Returns the data format, in one of the |
||||
* {@link android.graphics.ImageFormat} constants. |
||||
* This will always be {@link android.graphics.ImageFormat#NV21} for now. |
||||
* |
||||
* @return the data format |
||||
* @see android.graphics.ImageFormat |
||||
*/ |
||||
public int getFormat() { |
||||
return mFormat; |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.WorkerThread; |
||||
|
||||
/** |
||||
* A FrameProcessor will process {@link Frame}s coming from the camera preview. |
||||
* It must be passed to {@link CameraView#addFrameProcessor(FrameProcessor)}. |
||||
*/ |
||||
public interface FrameProcessor { |
||||
|
||||
/** |
||||
* Processes the given frame. The frame will hold the correct values only for the |
||||
* duration of this method. When it returns, the frame contents will be replaced. |
||||
* |
||||
* To keep working with the Frame in an async manner, please use {@link Frame#freeze()}, |
||||
* which will return an immutable Frame. In that case you can pass / hold the frame for |
||||
* as long as you want, and then release its contents using {@link Frame#release()}. |
||||
* |
||||
* @param frame the new frame |
||||
*/ |
||||
@WorkerThread |
||||
void process(@NonNull Frame frame); |
||||
} |
@ -0,0 +1,78 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
|
||||
import android.graphics.ImageFormat; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import static org.junit.Assert.assertArrayEquals; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertFalse; |
||||
import static org.junit.Assert.assertNotEquals; |
||||
import static org.junit.Assert.assertNull; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
public class FrameTest { |
||||
|
||||
@Test |
||||
public void testDefaults() { |
||||
Frame frame = new Frame(); |
||||
assertEquals(frame.getTime(), -1); |
||||
assertEquals(frame.getFormat(), -1); |
||||
assertEquals(frame.getRotation(), 0); |
||||
assertNull(frame.getData()); |
||||
assertNull(frame.getSize()); |
||||
} |
||||
|
||||
@Test |
||||
public void testEquals() { |
||||
Frame f1 = new Frame(); |
||||
long time = 1000; |
||||
f1.set(null, time, 90, null, ImageFormat.NV21); |
||||
Frame f2 = new Frame(); |
||||
f2.set(new byte[2], time, 0, new Size(10, 10), ImageFormat.NV21); |
||||
assertEquals(f1, f2); |
||||
|
||||
f2.set(new byte[2], time + 1, 0, new Size(10, 10), ImageFormat.NV21); |
||||
assertNotEquals(f1, f2); |
||||
} |
||||
|
||||
@Test |
||||
public void testRelease() { |
||||
Frame frame = new Frame(); |
||||
frame.set(new byte[2], 1000, 90, new Size(10, 10), ImageFormat.NV21); |
||||
frame.release(); |
||||
|
||||
assertEquals(frame.getTime(), -1); |
||||
assertEquals(frame.getFormat(), -1); |
||||
assertEquals(frame.getRotation(), 0); |
||||
assertNull(frame.getData()); |
||||
assertNull(frame.getSize()); |
||||
} |
||||
|
||||
@Test |
||||
public void testFreeze() { |
||||
Frame frame = new Frame(); |
||||
byte[] data = new byte[]{0, 1, 5, 0, 7, 3, 4, 5}; |
||||
long time = 1000; |
||||
int rotation = 90; |
||||
Size size = new Size(10, 10); |
||||
int format = ImageFormat.NV21; |
||||
frame.set(data, time, rotation, size, format); |
||||
|
||||
Frame frozen = frame.freeze(); |
||||
assertArrayEquals(data, frozen.getData()); |
||||
assertEquals(time, frozen.getTime()); |
||||
assertEquals(rotation, frozen.getRotation()); |
||||
assertEquals(size, frozen.getSize()); |
||||
|
||||
// Mutate the first, ensure that frozen is not affected
|
||||
frame.set(new byte[]{3, 2, 1}, 50, 180, new Size(1, 1), ImageFormat.JPEG); |
||||
assertArrayEquals(data, frozen.getData()); |
||||
assertEquals(time, frozen.getTime()); |
||||
assertEquals(rotation, frozen.getRotation()); |
||||
assertEquals(size, frozen.getSize()); |
||||
assertEquals(format, frozen.getFormat()); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue