diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java index f2f025d2..7271731f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java @@ -56,7 +56,7 @@ public class AudioMediaEncoder extends MediaEncoder { // This value is the number of runnables that the encoder thread is allowed to be 'behind' // the recorder thread. It's not safe to have it very large or we can end encoding A LOT AFTER // the actual recording. It's better to reduce this and skip recording at all. - private static final int BUFFER_POOL_MAX_SIZE = 100; + private static final int BUFFER_POOL_MAX_SIZE = 80; private static long bytesToUs(int bytes) { return (1000000L * bytes) / BYTE_RATE; @@ -182,11 +182,11 @@ public class AudioMediaEncoder extends MediaEncoder { // Sleeping before returning is a good way of balancing the two operations. // However, if endOfStream, we CAN'T lose this frame! if (endOfStream) { - LOG.v("read thread - eos:", endOfStream, "- No buffer, retrying."); + LOG.v("read thread - eos: true - No buffer, retrying."); read(true); // try again } else { - LOG.w("read thread - eos:", endOfStream, "- Skipping audio frame, encoding is too slow."); - sleep(); + LOG.w("read thread - eos: false - Skipping audio frame, encoding is too slow."); + sleep(); // sleep a bit } } else { mCurrentBuffer.clear(); @@ -290,8 +290,9 @@ public class AudioMediaEncoder extends MediaEncoder { @SuppressLint("HandlerLeak") class AudioEncodingHandler extends Handler { - InputBufferPool mInputBufferPool = new InputBufferPool(); - LinkedBlockingQueue mPendingOps = new LinkedBlockingQueue<>(); + private InputBufferPool mInputBufferPool = new InputBufferPool(); + private LinkedBlockingQueue mPendingOps = new LinkedBlockingQueue<>(); + private long mFirstFrameTimestamp = Long.MIN_VALUE; AudioEncodingHandler() { super(WorkerHandler.get("AudioEncodingHandler").getLooper()); @@ -310,6 +311,10 @@ public class AudioMediaEncoder extends MediaEncoder { super.handleMessage(msg); boolean endOfStream = msg.what == 1; long timestamp = (((long) msg.arg1) << 32) | (((long) msg.arg2) & 0xffffffffL); + if (mFirstFrameTimestamp == Long.MIN_VALUE) { + mFirstFrameTimestamp = timestamp; + } + LOG.i("encoding thread - got buffer. timestamp:", timestamp, "eos:", endOfStream); ByteBuffer buffer = (ByteBuffer) msg.obj; int readBytes = buffer.remaining(); @@ -319,6 +324,7 @@ public class AudioMediaEncoder extends MediaEncoder { inputBuffer.timestamp = timestamp; inputBuffer.length = readBytes; inputBuffer.isEndOfStream = endOfStream; + inputBuffer.didReachMaxLength = (timestamp - mFirstFrameTimestamp) > getMaxLengthMillis() * 1000; mPendingOps.add(inputBuffer); LOG.i("encoding thread - performing", mPendingOps.size(), "pending operations."); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/InputBuffer.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/InputBuffer.java index 979ef268..a90674f1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/InputBuffer.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/InputBuffer.java @@ -13,4 +13,5 @@ class InputBuffer { int length; long timestamp; boolean isEndOfStream; + boolean didReachMaxLength; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java index f90b0822..0b709e67 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoder.java @@ -55,6 +55,11 @@ import java.nio.ByteBuffer; * * For VIDEO encoders, things are much easier because we skip the whole input part. * See description in {@link VideoMediaEncoder}. + * + * For max length constraint, it will be checked automatically during {@link #drainOutput(boolean)}, + * or subclasses can provide an hint to this encoder using {@link InputBuffer#didReachMaxLength}. + * In this second case, we can request a stop at reading time, so we avoid useless readings + * in certain setups (where drain is called a lot after reading). */ // https://github.com/saki4510t/AudioVideoRecordingSample/blob/master/app/src/main/java/com/serenegiant/encoder/MediaEncoder.java @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @@ -100,9 +105,12 @@ abstract class MediaEncoder { private OutputBufferPool mOutputBufferPool; private MediaCodec.BufferInfo mBufferInfo; private MediaCodecBuffers mBuffers; + private long mMaxLengthMillis; private boolean mMaxLengthReached; + private long mStartPresentationTimeUs = Long.MIN_VALUE; + private long mLastPresentationTimeUs = 0; /** * Needs a readable name for the thread and for logging. * @param name a name @@ -334,6 +342,9 @@ abstract class MediaEncoder { mMediaCodec.queueInputBuffer(buffer.index, 0, buffer.length, buffer.timestamp, 0); } + if (buffer.didReachMaxLength) { + onMaxLengthReached(); + } } /** @@ -421,10 +432,7 @@ abstract class MediaEncoder { LOG.w(mName, "DRAINING - Reached maxLength! mLastPresentationTimeUs:", mLastPresentationTimeUs, "mStartPresentationTimeUs:", mStartPresentationTimeUs, "mMaxLengthUs:", mMaxLengthMillis * 1000); - mMaxLengthReached = true; - LOG.w(mName, "DRAINING - Requesting a stop."); - setState(STATE_LIMIT_REACHED); - mController.requestStop(mTrackIndex); + onMaxLengthReached(); break; } @@ -438,8 +446,21 @@ abstract class MediaEncoder { } } - private long mStartPresentationTimeUs = Long.MIN_VALUE; - private long mLastPresentationTimeUs = 0; - abstract int getEncodedBitRate(); + + private void onMaxLengthReached() { + if (mMaxLengthReached) return; + mMaxLengthReached = true; + if (mState >= STATE_LIMIT_REACHED) { + LOG.w(mName, "onMaxLengthReached: Reached in wrong state. Aborting.", mState); + } else { + LOG.w(mName, "onMaxLengthReached: Requesting a stop."); + setState(STATE_LIMIT_REACHED); + mController.requestStop(mTrackIndex); + } + } + + protected long getMaxLengthMillis() { + return mMaxLengthMillis; + } }