Use a Pool for float[] arrays

pull/374/head
Mattia Iavarone 7 years ago
parent 8d28352179
commit a4ecff50b0
  1. 6
      cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java
  2. 14
      cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java
  3. 10
      cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java
  4. 46
      cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java
  5. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java

@ -46,9 +46,9 @@ class AudioMediaEncoder extends MediaEncoder {
private static final int FRAME_SIZE = FRAME_SIZE_PER_CHANNEL * CHANNELS_COUNT; // bytes/frame private static final int FRAME_SIZE = FRAME_SIZE_PER_CHANNEL * CHANNELS_COUNT; // bytes/frame
// We allocate buffers of 1KB each, which is not so much. I would say that allocating // We allocate buffers of 1KB each, which is not so much. I would say that allocating
// at most 100 of them is a reasonable value. With the current setup, in device tests, // at most 200 of them is a reasonable value. With the current setup, in device tests,
// we manage to use 5 at most. // we manage to use 50 at most.
private static final int BUFFER_POOL_MAX_SIZE = 100; private static final int BUFFER_POOL_MAX_SIZE = 200;
private boolean mRequestStop = false; private boolean mRequestStop = false;
private AudioEncodingHandler mEncoder; private AudioEncodingHandler mEncoder;

@ -210,6 +210,20 @@ class MediaEncoderEngine {
mMediaMuxerStarted = false; mMediaMuxerStarted = false;
} }
@NonNull
VideoMediaEncoder getVideoEncoder() {
return (VideoMediaEncoder) mEncoders.get(0);
}
@Nullable
AudioMediaEncoder getAudioEncoder() {
if (mEncoders.size() > 1) {
return (AudioMediaEncoder) mEncoders.get(1);
} else {
return null;
}
}
interface Listener { interface Listener {
@EncoderThread @EncoderThread

@ -26,9 +26,13 @@ class Pool<T> {
this.factory = factory; this.factory = factory;
} }
boolean canGet() {
return count() < maxPoolSize;
}
@Nullable @Nullable
T get() { T get() {
if (count() >= maxPoolSize) { if (!canGet()) {
LOG.v("GET: Returning null. Too much items requested."); LOG.v("GET: Returning null. Too much items requested.");
return null; return null;
} }
@ -46,14 +50,14 @@ class Pool<T> {
} }
void recycle(@NonNull T buffer) { void recycle(@NonNull T item) {
LOG.v("RECYCLE: Recycling item. Count", count(), "Active", activeCount(), "Cached", cachedCount()); LOG.v("RECYCLE: Recycling item. Count", count(), "Active", activeCount(), "Cached", cachedCount());
if (--activeCount < 0) { if (--activeCount < 0) {
throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." + throw new IllegalStateException("Trying to recycle an item which makes activeCount < 0." +
"This means that this or some previous items being recycled were not coming from " + "This means that this or some previous items being recycled were not coming from " +
"this pool, or some item was recycled more than once."); "this pool, or some item was recycled more than once.");
} }
if (!mQueue.offer(buffer)) { if (!mQueue.offer(item)) {
throw new IllegalStateException("Trying to recycle an item while the queue is full. " + throw new IllegalStateException("Trying to recycle an item while the queue is full. " +
"This means that this or some previous items being recycled were not coming from " + "This means that this or some previous items being recycled were not coming from " +
"this pool, or some item was recycled more than once."); "this pool, or some item was recycled more than once.");

@ -3,6 +3,8 @@ package com.otaliastudios.cameraview;
import android.opengl.EGLContext; import android.opengl.EGLContext;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Build; import android.os.Build;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
@ -15,13 +17,6 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
final static String FRAME_EVENT = "frame"; final static String FRAME_EVENT = "frame";
static class Frame {
float[] transform;
// Nanoseconds, in no meaningful time-base. Should be for offsets only.
// Typically coming from SurfaceTexture.getTimestamp().
long timestamp;
}
static class Config extends VideoMediaEncoder.Config { static class Config extends VideoMediaEncoder.Config {
int textureId; int textureId;
float scaleX; float scaleX;
@ -47,11 +42,36 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
private EglCore mEglCore; private EglCore mEglCore;
private EglWindowSurface mWindow; private EglWindowSurface mWindow;
private EglViewport mViewport; private EglViewport mViewport;
private Pool<TextureFrame> mFramePool = new Pool<>(30, new Pool.Factory<TextureFrame>() {
@Override
public TextureFrame create() {
return new TextureFrame();
}
});
TextureMediaEncoder(@NonNull Config config) { TextureMediaEncoder(@NonNull Config config) {
super(config); super(config);
} }
static class TextureFrame {
private TextureFrame() {}
// Nanoseconds, in no meaningful time-base. Should be for offsets only.
// Typically coming from SurfaceTexture.getTimestamp().
long timestamp;
float[] transform = new float[16];
}
@NonNull
TextureFrame acquireFrame() {
if (!mFramePool.canGet()) {
throw new RuntimeException("Need more frames than this! Please increase the pool size.");
} else {
//noinspection ConstantConditions
return mFramePool.get();
}
}
@EncoderThread @EncoderThread
@Override @Override
void onPrepare(@NonNull MediaEncoderEngine.Controller controller, long maxLengthMillis) { void onPrepare(@NonNull MediaEncoderEngine.Controller controller, long maxLengthMillis) {
@ -74,13 +94,17 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
@Override @Override
void onEvent(@NonNull String event, @Nullable Object data) { void onEvent(@NonNull String event, @Nullable Object data) {
if (!event.equals(FRAME_EVENT)) return; if (!event.equals(FRAME_EVENT)) return;
Frame frame = (Frame) data; TextureFrame frame = (TextureFrame) data;
if (frame == null) return; // Should not happen if (frame == null) return; // Should not happen
if (frame.timestamp == 0) return; // Read in Grafika if (frame.timestamp == 0 || mFrameNum < 0) {
if (mFrameNum < 0) return; // We were asked to stop. Ignore frame // The first condition comes from grafika.
// The second condition means we were asked to stop.
mFramePool.recycle(frame);
return;
}
mFrameNum++; mFrameNum++;
LOG.v("Incoming frame timestamp:", frame.timestamp); LOG.v("Incoming frame timestamp:", frame.timestamp);
// We must scale this matrix like GlCameraPreview does, because it might have some cropping. // We must scale this matrix like GlCameraPreview does, because it might have some cropping.
// Scaling takes place with respect to the (0, 0, 0) point, so we must apply a Translation to compensate. // Scaling takes place with respect to the (0, 0, 0) point, so we must apply a Translation to compensate.
float[] transform = frame.transform; float[] transform = frame.transform;

@ -96,11 +96,11 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren
} }
if (mCurrentState == STATE_RECORDING) { if (mCurrentState == STATE_RECORDING) {
TextureMediaEncoder.Frame frame = new TextureMediaEncoder.Frame(); TextureMediaEncoder textureEncoder = (TextureMediaEncoder) mEncoderEngine.getVideoEncoder();
frame.timestamp = surfaceTexture.getTimestamp(); TextureMediaEncoder.TextureFrame textureFrame = textureEncoder.acquireFrame();
frame.transform = new float[16]; // TODO would be cool to avoid this at every frame. But it's not easy. textureFrame.timestamp = surfaceTexture.getTimestamp();
surfaceTexture.getTransformMatrix(frame.transform); surfaceTexture.getTransformMatrix(textureFrame.transform);
mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, frame); mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, textureFrame);
} }
if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) { if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) {

Loading…
Cancel
Save