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