|
|
@ -1,6 +1,8 @@ |
|
|
|
package com.otaliastudios.cameraview.frame; |
|
|
|
package com.otaliastudios.cameraview.frame; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import android.graphics.ImageFormat; |
|
|
|
|
|
|
|
|
|
|
|
import com.otaliastudios.cameraview.CameraLogger; |
|
|
|
import com.otaliastudios.cameraview.CameraLogger; |
|
|
|
import com.otaliastudios.cameraview.size.Size; |
|
|
|
import com.otaliastudios.cameraview.size.Size; |
|
|
|
|
|
|
|
|
|
|
@ -21,7 +23,7 @@ import java.util.concurrent.LinkedBlockingQueue; |
|
|
|
* Main methods are: |
|
|
|
* Main methods are: |
|
|
|
* - {@link #setUp(int, Size)}: to set up with size and allocate buffers |
|
|
|
* - {@link #setUp(int, Size)}: to set up with size and allocate buffers |
|
|
|
* - {@link #release()}: to release. After release, a manager can be setUp again. |
|
|
|
* - {@link #release()}: to release. After release, a manager can be setUp again. |
|
|
|
* - {@link #getFrame(byte[], long, int, Size, int)}: gets a new {@link Frame}. |
|
|
|
* - {@link #getFrame(byte[], long, int)}: gets a new {@link Frame}. |
|
|
|
* |
|
|
|
* |
|
|
|
* For both byte buffers and frames to get back to the FrameManager pool, all you have to do |
|
|
|
* For both byte buffers and frames to get back to the FrameManager pool, all you have to do |
|
|
|
* is call {@link Frame#release()} when done. |
|
|
|
* is call {@link Frame#release()} when done. |
|
|
@ -31,12 +33,12 @@ import java.util.concurrent.LinkedBlockingQueue; |
|
|
|
* |
|
|
|
* |
|
|
|
* 1. {@link #BUFFER_MODE_DISPATCH}: in this mode, as soon as we have a buffer, it is dispatched to |
|
|
|
* 1. {@link #BUFFER_MODE_DISPATCH}: in this mode, as soon as we have a buffer, it is dispatched to |
|
|
|
* the {@link BufferCallback}. The callback should then fill the buffer, and finally call |
|
|
|
* the {@link BufferCallback}. The callback should then fill the buffer, and finally call |
|
|
|
* {@link #getFrame(byte[], long, int, Size, int)} to receive a frame. |
|
|
|
* {@link #getFrame(byte[], long, int)} to receive a frame. |
|
|
|
* This is used for Camera1. |
|
|
|
* This is used for Camera1. |
|
|
|
* |
|
|
|
* |
|
|
|
* 2. {@link #BUFFER_MODE_ENQUEUE}: in this mode, the manager internally keeps a queue of byte buffers, |
|
|
|
* 2. {@link #BUFFER_MODE_ENQUEUE}: in this mode, the manager internally keeps a queue of byte buffers, |
|
|
|
* instead of handing them to the callback. The users can ask for buffers through {@link #getBuffer()}. |
|
|
|
* instead of handing them to the callback. The users can ask for buffers through {@link #getBuffer()}. |
|
|
|
* This buffer can be filled with data and used to get a frame {@link #getFrame(byte[], long, int, Size, int)}, |
|
|
|
* This buffer can be filled with data and used to get a frame {@link #getFrame(byte[], long, int)}, |
|
|
|
* or, in case it was not filled, returned to the queue using {@link #onBufferUnused(byte[])}. |
|
|
|
* or, in case it was not filled, returned to the queue using {@link #onBufferUnused(byte[])}. |
|
|
|
* This is used for Camera2. |
|
|
|
* This is used for Camera2. |
|
|
|
*/ |
|
|
|
*/ |
|
|
@ -55,6 +57,8 @@ public class FrameManager { |
|
|
|
|
|
|
|
|
|
|
|
private final int mPoolSize; |
|
|
|
private final int mPoolSize; |
|
|
|
private int mBufferSize = -1; |
|
|
|
private int mBufferSize = -1; |
|
|
|
|
|
|
|
private Size mFrameSize = null; |
|
|
|
|
|
|
|
private int mFrameFormat = -1; |
|
|
|
private LinkedBlockingQueue<Frame> mFrameQueue; |
|
|
|
private LinkedBlockingQueue<Frame> mFrameQueue; |
|
|
|
private LinkedBlockingQueue<byte[]> mBufferQueue; |
|
|
|
private LinkedBlockingQueue<byte[]> mBufferQueue; |
|
|
|
private BufferCallback mBufferCallback; |
|
|
|
private BufferCallback mBufferCallback; |
|
|
@ -94,17 +98,22 @@ public class FrameManager { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Allocates a {@link #mPoolSize} number of buffers. Should be called once |
|
|
|
* Allocates a {@link #mPoolSize} number of buffers. Should be called once |
|
|
|
* the preview size and the bitsPerPixel value are known. |
|
|
|
* the preview size and the image format value are known. |
|
|
|
* |
|
|
|
* |
|
|
|
* This method can be called again after {@link #release()} has been called. |
|
|
|
* This method can be called again after {@link #release()} has been called. |
|
|
|
* |
|
|
|
* |
|
|
|
* @param bitsPerPixel bits per pixel, depends on image format |
|
|
|
* @param format the image format |
|
|
|
* @param previewSize the preview size |
|
|
|
* @param size the frame size |
|
|
|
* @return the buffer size |
|
|
|
* @return the buffer size |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public int setUp(int bitsPerPixel, @NonNull Size previewSize) { |
|
|
|
public int setUp(int format, @NonNull Size size) { |
|
|
|
// TODO throw if called twice without release?
|
|
|
|
if (isSetUp()) { |
|
|
|
long sizeInBits = previewSize.getHeight() * previewSize.getWidth() * bitsPerPixel; |
|
|
|
// TODO throw or just reconfigure?
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mFrameSize = size; |
|
|
|
|
|
|
|
mFrameFormat = format; |
|
|
|
|
|
|
|
int bitsPerPixel = ImageFormat.getBitsPerPixel(format); |
|
|
|
|
|
|
|
long sizeInBits = size.getHeight() * size.getWidth() * bitsPerPixel; |
|
|
|
mBufferSize = (int) Math.ceil(sizeInBits / 8.0d); |
|
|
|
mBufferSize = (int) Math.ceil(sizeInBits / 8.0d); |
|
|
|
for (int i = 0; i < mPoolSize; i++) { |
|
|
|
for (int i = 0; i < mPoolSize; i++) { |
|
|
|
if (mBufferMode == BUFFER_MODE_DISPATCH) { |
|
|
|
if (mBufferMode == BUFFER_MODE_DISPATCH) { |
|
|
@ -116,13 +125,24 @@ public class FrameManager { |
|
|
|
return mBufferSize; |
|
|
|
return mBufferSize; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Returns true after {@link #setUp(int, Size)} |
|
|
|
|
|
|
|
* but before {@link #release()}. |
|
|
|
|
|
|
|
* Returns false otherwise. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @return true if set up |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean isSetUp() { |
|
|
|
|
|
|
|
return mFrameSize != null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Returns a new byte buffer than can be filled. |
|
|
|
* Returns a new byte buffer than can be filled. |
|
|
|
* This can only be called in {@link #BUFFER_MODE_ENQUEUE} mode! Where the frame |
|
|
|
* This can only be called in {@link #BUFFER_MODE_ENQUEUE} mode! Where the frame |
|
|
|
* manager also holds a queue of the byte buffers. |
|
|
|
* manager also holds a queue of the byte buffers. |
|
|
|
* |
|
|
|
* |
|
|
|
* If not null, the buffer returned by this method can be filled and used to get |
|
|
|
* If not null, the buffer returned by this method can be filled and used to get |
|
|
|
* a new frame through {@link #getFrame(byte[], long, int, Size, int)}. |
|
|
|
* a new frame through {@link #getFrame(byte[], long, int)}. |
|
|
|
* |
|
|
|
* |
|
|
|
* @return a buffer, or null |
|
|
|
* @return a buffer, or null |
|
|
|
*/ |
|
|
|
*/ |
|
|
@ -143,7 +163,12 @@ public class FrameManager { |
|
|
|
if (mBufferMode != BUFFER_MODE_ENQUEUE) { |
|
|
|
if (mBufferMode != BUFFER_MODE_ENQUEUE) { |
|
|
|
throw new IllegalStateException("Can't call onBufferUnused() when not in BUFFER_MODE_ENQUEUE."); |
|
|
|
throw new IllegalStateException("Can't call onBufferUnused() when not in BUFFER_MODE_ENQUEUE."); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isSetUp()) { |
|
|
|
mBufferQueue.offer(buffer); |
|
|
|
mBufferQueue.offer(buffer); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
LOG.w("onBufferUnused: buffer was returned but we're not set up anymore."); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -158,53 +183,38 @@ public class FrameManager { |
|
|
|
* @param data data |
|
|
|
* @param data data |
|
|
|
* @param time timestamp |
|
|
|
* @param time timestamp |
|
|
|
* @param rotation rotation |
|
|
|
* @param rotation rotation |
|
|
|
* @param previewSize preview size |
|
|
|
|
|
|
|
* @param previewFormat format |
|
|
|
|
|
|
|
* @return a new frame |
|
|
|
* @return a new frame |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
public Frame getFrame(@NonNull byte[] data, long time, int rotation, @NonNull Size previewSize, int previewFormat) { |
|
|
|
public Frame getFrame(@NonNull byte[] data, long time, int rotation) { |
|
|
|
|
|
|
|
if (!isSetUp()) { |
|
|
|
|
|
|
|
throw new IllegalStateException("Can't call getFrame() after releasing or before setUp."); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Frame frame = mFrameQueue.poll(); |
|
|
|
Frame frame = mFrameQueue.poll(); |
|
|
|
if (frame != null) { |
|
|
|
if (frame != null) { |
|
|
|
LOG.v("getFrame for time:", time, "RECYCLING.", "Data:", data != null); |
|
|
|
LOG.v("getFrame for time:", time, "RECYCLING."); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
LOG.v("getFrame for time:", time, "CREATING.", "Data:", data != null); |
|
|
|
LOG.v("getFrame for time:", time, "CREATING."); |
|
|
|
frame = new Frame(this); |
|
|
|
frame = new Frame(this); |
|
|
|
} |
|
|
|
} |
|
|
|
frame.set(data, time, rotation, previewSize, previewFormat); |
|
|
|
frame.setContent(data, time, rotation, mFrameSize, mFrameFormat); |
|
|
|
return frame; |
|
|
|
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() { |
|
|
|
|
|
|
|
LOG.w("Releasing all frames!"); |
|
|
|
|
|
|
|
for (Frame frame : mFrameQueue) { |
|
|
|
|
|
|
|
frame.releaseManager(); |
|
|
|
|
|
|
|
frame.release(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mFrameQueue.clear(); |
|
|
|
|
|
|
|
if (mBufferMode == BUFFER_MODE_ENQUEUE) { |
|
|
|
|
|
|
|
mBufferQueue.clear(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mBufferSize = -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Called by child frames when they are released. |
|
|
|
* Called by child frames when they are released. |
|
|
|
|
|
|
|
* This might be called from old Frames that belong to an old 'setUp' |
|
|
|
|
|
|
|
* of this FrameManager instance. So the buffer size might be different, |
|
|
|
|
|
|
|
* for instance. |
|
|
|
|
|
|
|
* |
|
|
|
* @param frame the released frame |
|
|
|
* @param frame the released frame |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
void onFrameReleased(@NonNull Frame frame) { |
|
|
|
void onFrameReleased(@NonNull Frame frame, @NonNull byte[] buffer) { |
|
|
|
byte[] buffer = frame.getData(); |
|
|
|
if (!isSetUp()) return; |
|
|
|
boolean willRecycle = mFrameQueue.offer(frame); |
|
|
|
|
|
|
|
if (!willRecycle) { |
|
|
|
|
|
|
|
// If frame queue is full, let's drop everything.
|
|
|
|
// If frame queue is full, let's drop everything.
|
|
|
|
frame.releaseManager(); |
|
|
|
// If frame queue accepts this frame, let's recycle the buffer as well.
|
|
|
|
} else { |
|
|
|
if (mFrameQueue.offer(frame)) { |
|
|
|
// If frame will be recycled, let's recycle the buffer as well.
|
|
|
|
|
|
|
|
int currSize = buffer.length; |
|
|
|
int currSize = buffer.length; |
|
|
|
int reqSize = mBufferSize; |
|
|
|
int reqSize = mBufferSize; |
|
|
|
if (currSize == reqSize) { |
|
|
|
if (currSize == reqSize) { |
|
|
@ -216,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; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|