|
|
@ -56,10 +56,22 @@ import java.nio.ByteBuffer; |
|
|
|
* For VIDEO encoders, things are much easier because we skip the whole input part. |
|
|
|
* For VIDEO encoders, things are much easier because we skip the whole input part. |
|
|
|
* See description in {@link VideoMediaEncoder}. |
|
|
|
* See description in {@link VideoMediaEncoder}. |
|
|
|
* |
|
|
|
* |
|
|
|
|
|
|
|
* MAX LENGTH CONSTRAINT |
|
|
|
|
|
|
|
* |
|
|
|
* For max length constraint, it will be checked automatically during {@link #drainOutput(boolean)}, |
|
|
|
* For max length constraint, it will be checked automatically during {@link #drainOutput(boolean)}, |
|
|
|
* OR subclasses can provide an hint to this encoder using {@link #notifyMaxLengthReached()}. |
|
|
|
* OR subclasses can provide an hint to this encoder using {@link #notifyMaxLengthReached()}. |
|
|
|
* In this second case, we can request a stop at reading time, so we avoid useless readings |
|
|
|
* 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). |
|
|
|
* in certain setups (where drain is called a lot after reading). |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* TIMING |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* Subclasses can use timestamps (in microseconds) in any reference system they prefer. For |
|
|
|
|
|
|
|
* instance, it might be the {@link System#nanoTime()} reference, or some reference provided |
|
|
|
|
|
|
|
* by SurfaceTextures. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* However, they are required to call {@link #notifyFirstFrameMillis(long)} and pass the |
|
|
|
|
|
|
|
* milliseconds of the first frame in the {@link System#currentTimeMillis()} reference, so |
|
|
|
|
|
|
|
* something that we can coordinate on. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
// https://github.com/saki4510t/AudioVideoRecordingSample/blob/master/app/src/main/java/com/serenegiant/encoder/MediaEncoder.java
|
|
|
|
// https://github.com/saki4510t/AudioVideoRecordingSample/blob/master/app/src/main/java/com/serenegiant/encoder/MediaEncoder.java
|
|
|
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
|
|
|
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
|
|
@ -109,8 +121,10 @@ abstract class MediaEncoder { |
|
|
|
private long mMaxLengthMillis; |
|
|
|
private long mMaxLengthMillis; |
|
|
|
private boolean mMaxLengthReached; |
|
|
|
private boolean mMaxLengthReached; |
|
|
|
|
|
|
|
|
|
|
|
private long mStartPresentationTimeUs = Long.MIN_VALUE; |
|
|
|
private long mStartTimeMillis = 0; // In System.currentTimeMillis()
|
|
|
|
private long mLastPresentationTimeUs = 0; |
|
|
|
private long mStartTimeUs = Long.MIN_VALUE; // In unknown reference
|
|
|
|
|
|
|
|
private long mLastTimeUs = 0; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Needs a readable name for the thread and for logging. |
|
|
|
* Needs a readable name for the thread and for logging. |
|
|
|
* @param name a name |
|
|
|
* @param name a name |
|
|
@ -264,7 +278,7 @@ abstract class MediaEncoder { |
|
|
|
* @param data object |
|
|
|
* @param data object |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@EncoderThread |
|
|
|
@EncoderThread |
|
|
|
void onEvent(@NonNull String event, @Nullable Object data) {}; |
|
|
|
void onEvent(@NonNull String event, @Nullable Object data) {} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Stop recording. This involves signaling the end of stream and draining |
|
|
|
* Stop recording. This involves signaling the end of stream and draining |
|
|
@ -392,26 +406,29 @@ abstract class MediaEncoder { |
|
|
|
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
|
|
|
|
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
|
|
|
|
boolean isCodecConfig = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; |
|
|
|
boolean isCodecConfig = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; |
|
|
|
if (!isCodecConfig && mController.isStarted() && mBufferInfo.size != 0) { |
|
|
|
if (!isCodecConfig && mController.isStarted() && mBufferInfo.size != 0) { |
|
|
|
|
|
|
|
|
|
|
|
// adjust the ByteBuffer values to match BufferInfo (not needed?)
|
|
|
|
// adjust the ByteBuffer values to match BufferInfo (not needed?)
|
|
|
|
encodedData.position(mBufferInfo.offset); |
|
|
|
encodedData.position(mBufferInfo.offset); |
|
|
|
encodedData.limit(mBufferInfo.offset + mBufferInfo.size); |
|
|
|
encodedData.limit(mBufferInfo.offset + mBufferInfo.size); |
|
|
|
// Store startPresentationTime and lastPresentationTime, useful for example to
|
|
|
|
|
|
|
|
// detect the mMaxLengthReached and stop recording.
|
|
|
|
// Store mStartTimeUs and mLastTimeUs, useful to detect the max length
|
|
|
|
if (mStartPresentationTimeUs == Long.MIN_VALUE) { |
|
|
|
// reached and stop recording when needed.
|
|
|
|
mStartPresentationTimeUs = mBufferInfo.presentationTimeUs; |
|
|
|
if (mStartTimeUs == Long.MIN_VALUE) { |
|
|
|
LOG.w(mName, "DRAINING - Got the first presentation time:", mStartPresentationTimeUs); |
|
|
|
mStartTimeUs = mBufferInfo.presentationTimeUs; |
|
|
|
|
|
|
|
LOG.w(mName, "DRAINING - Got the first presentation time:", mStartTimeUs); |
|
|
|
} |
|
|
|
} |
|
|
|
mLastPresentationTimeUs = mBufferInfo.presentationTimeUs; |
|
|
|
mLastTimeUs = mBufferInfo.presentationTimeUs; |
|
|
|
// Pass presentation times as offets with respect to the mStartPresentationTimeUs.
|
|
|
|
|
|
|
|
// This ensures consistency between audio pts (coming from System.nanoTime()) and
|
|
|
|
// Adjust the presentation times. Subclasses can pass a presentation time in any
|
|
|
|
// video pts (coming from SurfaceTexture) both of which have no meaningful time-base
|
|
|
|
// reference system - possibly some that has no real meaning, and frequently,
|
|
|
|
// and should be used for offsets only.
|
|
|
|
// presentation times from different encoders have a different time-base.
|
|
|
|
// TODO find a better way, this causes sync issues. (+ note: this sends pts=0 at first)
|
|
|
|
// To address this, encoders are required to call notifyFirstFrameMillis
|
|
|
|
// mBufferInfo.presentationTimeUs = mLastPresentationTimeUs - mStartPresentationTimeUs;
|
|
|
|
// so we can adjust here - moving to 1970 reference.
|
|
|
|
LOG.v(mName, "DRAINING - About to write(). Presentation:", mBufferInfo.presentationTimeUs); |
|
|
|
// Extra benefit: we never pass a pts equal to 0, which some encoders refuse.
|
|
|
|
|
|
|
|
mBufferInfo.presentationTimeUs = (mStartTimeMillis * 1000) + mLastTimeUs - mStartTimeUs; |
|
|
|
// TODO fix the mBufferInfo being the same, then implement delayed writing in Controller
|
|
|
|
|
|
|
|
// and remove the isStarted() check here.
|
|
|
|
// Write.
|
|
|
|
|
|
|
|
LOG.v(mName, "DRAINING - About to write(). Adjusted presentation:", mBufferInfo.presentationTimeUs); |
|
|
|
OutputBuffer buffer = mOutputBufferPool.get(); |
|
|
|
OutputBuffer buffer = mOutputBufferPool.get(); |
|
|
|
//noinspection ConstantConditions
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
buffer.info = mBufferInfo; |
|
|
|
buffer.info = mBufferInfo; |
|
|
@ -425,10 +442,10 @@ abstract class MediaEncoder { |
|
|
|
// Not needed if drainAll because we already were asked to stop
|
|
|
|
// Not needed if drainAll because we already were asked to stop
|
|
|
|
if (!drainAll |
|
|
|
if (!drainAll |
|
|
|
&& !mMaxLengthReached |
|
|
|
&& !mMaxLengthReached |
|
|
|
&& mStartPresentationTimeUs != Long.MIN_VALUE |
|
|
|
&& mStartTimeUs != Long.MIN_VALUE |
|
|
|
&& mLastPresentationTimeUs - mStartPresentationTimeUs > mMaxLengthMillis * 1000) { |
|
|
|
&& mLastTimeUs - mStartTimeUs > mMaxLengthMillis * 1000) { |
|
|
|
LOG.w(mName, "DRAINING - Reached maxLength! mLastPresentationTimeUs:", mLastPresentationTimeUs, |
|
|
|
LOG.w(mName, "DRAINING - Reached maxLength! mLastTimeUs:", mLastTimeUs, |
|
|
|
"mStartPresentationTimeUs:", mStartPresentationTimeUs, |
|
|
|
"mStartTimeUs:", mStartTimeUs, |
|
|
|
"mMaxLengthUs:", mMaxLengthMillis * 1000); |
|
|
|
"mMaxLengthUs:", mMaxLengthMillis * 1000); |
|
|
|
onMaxLengthReached(); |
|
|
|
onMaxLengthReached(); |
|
|
|
break; |
|
|
|
break; |
|
|
@ -446,15 +463,33 @@ abstract class MediaEncoder { |
|
|
|
|
|
|
|
|
|
|
|
abstract int getEncodedBitRate(); |
|
|
|
abstract int getEncodedBitRate(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Returns the max length setting, in milliseconds, which can be used |
|
|
|
|
|
|
|
* to compute the current state and eventually call {@link #notifyMaxLengthReached()}. |
|
|
|
|
|
|
|
* This is not a requirement for subclasses - we do this check anyway when draining, |
|
|
|
|
|
|
|
* but doing so might be better. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @return the max length setting |
|
|
|
|
|
|
|
*/ |
|
|
|
@SuppressWarnings("WeakerAccess") |
|
|
|
@SuppressWarnings("WeakerAccess") |
|
|
|
protected long getMaxLengthMillis() { |
|
|
|
protected long getMaxLengthMillis() { |
|
|
|
return mMaxLengthMillis; |
|
|
|
return mMaxLengthMillis; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Called by subclasses to notify that the max length was reached. |
|
|
|
|
|
|
|
* We will move to {@link #STATE_LIMIT_REACHED} and request a stop. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@SuppressWarnings("WeakerAccess") |
|
|
|
protected void notifyMaxLengthReached() { |
|
|
|
protected void notifyMaxLengthReached() { |
|
|
|
onMaxLengthReached(); |
|
|
|
onMaxLengthReached(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Called by us (during {@link #drainOutput(boolean)}) or by subclasses |
|
|
|
|
|
|
|
* (through {@link #notifyMaxLengthReached()}) to notify that we reached the |
|
|
|
|
|
|
|
* max length allowed. We will move to {@link #STATE_LIMIT_REACHED} and request a stop. |
|
|
|
|
|
|
|
*/ |
|
|
|
private void onMaxLengthReached() { |
|
|
|
private void onMaxLengthReached() { |
|
|
|
if (mMaxLengthReached) return; |
|
|
|
if (mMaxLengthReached) return; |
|
|
|
mMaxLengthReached = true; |
|
|
|
mMaxLengthReached = true; |
|
|
@ -466,4 +501,17 @@ abstract class MediaEncoder { |
|
|
|
mController.requestStop(mTrackIndex); |
|
|
|
mController.requestStop(mTrackIndex); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Should be called by subclasses to pass the milliseconds of the first frame - as soon |
|
|
|
|
|
|
|
* as this information is available. The milliseconds should be in the |
|
|
|
|
|
|
|
* {@link System#currentTimeMillis()} reference system, so we can coordinate between different |
|
|
|
|
|
|
|
* encoders. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param firstFrameMillis the milliseconds of the first frame presentation |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@SuppressWarnings("WeakerAccess") |
|
|
|
|
|
|
|
protected void notifyFirstFrameMillis(long firstFrameMillis) { |
|
|
|
|
|
|
|
mStartTimeMillis = firstFrameMillis; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|