diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java index e15a4c5c..6122a77d 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java @@ -46,9 +46,9 @@ class AudioMediaEncoder extends MediaEncoder { 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 - // at most 100 of them is a reasonable value. With the current setup, in device tests, - // we manage to use 5 at most. - private static final int BUFFER_POOL_MAX_SIZE = 100; + // at most 200 of them is a reasonable value. With the current setup, in device tests, + // we manage to use 50 at most. + private static final int BUFFER_POOL_MAX_SIZE = 200; private boolean mRequestStop = false; private AudioEncodingHandler mEncoder; diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java index e9886c63..4b210bcb 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java @@ -210,6 +210,20 @@ class MediaEncoderEngine { 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 { @EncoderThread diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java index 5890647f..61ebebb3 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/Pool.java @@ -26,9 +26,13 @@ class Pool { this.factory = factory; } + boolean canGet() { + return count() < maxPoolSize; + } + @Nullable T get() { - if (count() >= maxPoolSize) { + if (!canGet()) { LOG.v("GET: Returning null. Too much items requested."); return null; } @@ -46,14 +50,14 @@ class Pool { } - void recycle(@NonNull T buffer) { + void recycle(@NonNull T item) { LOG.v("RECYCLE: Recycling item. Count", count(), "Active", activeCount(), "Cached", cachedCount()); if (--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 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. " + "This means that this or some previous items being recycled were not coming from " + "this pool, or some item was recycled more than once."); diff --git a/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java b/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java index 2e635c8f..113a7c67 100644 --- a/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java +++ b/cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java @@ -3,6 +3,8 @@ package com.otaliastudios.cameraview; import android.opengl.EGLContext; import android.opengl.Matrix; import android.os.Build; +import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -15,13 +17,6 @@ class TextureMediaEncoder extends VideoMediaEncoder 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 { int textureId; float scaleX; @@ -47,11 +42,36 @@ class TextureMediaEncoder extends VideoMediaEncoder private EglCore mEglCore; private EglWindowSurface mWindow; private EglViewport mViewport; + private Pool mFramePool = new Pool<>(30, new Pool.Factory() { + @Override + public TextureFrame create() { + return new TextureFrame(); + } + }); TextureMediaEncoder(@NonNull Config 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 @Override void onPrepare(@NonNull MediaEncoderEngine.Controller controller, long maxLengthMillis) { @@ -74,13 +94,17 @@ class TextureMediaEncoder extends VideoMediaEncoder @Override void onEvent(@NonNull String event, @Nullable Object data) { if (!event.equals(FRAME_EVENT)) return; - Frame frame = (Frame) data; + TextureFrame frame = (TextureFrame) data; if (frame == null) return; // Should not happen - if (frame.timestamp == 0) return; // Read in Grafika - if (mFrameNum < 0) return; // We were asked to stop. Ignore frame + if (frame.timestamp == 0 || mFrameNum < 0) { + // The first condition comes from grafika. + // The second condition means we were asked to stop. + mFramePool.recycle(frame); + return; + } + mFrameNum++; LOG.v("Incoming frame timestamp:", frame.timestamp); - // 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. float[] transform = frame.transform; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java index abdb10ad..61d9429e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/SnapshotVideoRecorder.java @@ -96,11 +96,11 @@ class SnapshotVideoRecorder extends VideoRecorder implements GlCameraPreview.Ren } if (mCurrentState == STATE_RECORDING) { - TextureMediaEncoder.Frame frame = new TextureMediaEncoder.Frame(); - frame.timestamp = surfaceTexture.getTimestamp(); - frame.transform = new float[16]; // TODO would be cool to avoid this at every frame. But it's not easy. - surfaceTexture.getTransformMatrix(frame.transform); - mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, frame); + TextureMediaEncoder textureEncoder = (TextureMediaEncoder) mEncoderEngine.getVideoEncoder(); + TextureMediaEncoder.TextureFrame textureFrame = textureEncoder.acquireFrame(); + textureFrame.timestamp = surfaceTexture.getTimestamp(); + surfaceTexture.getTransformMatrix(textureFrame.transform); + mEncoderEngine.notify(TextureMediaEncoder.FRAME_EVENT, textureFrame); } if (mCurrentState == STATE_RECORDING && mDesiredState == STATE_NOT_RECORDING) {