Timestamp changes

pull/374/head
Mattia Iavarone 7 years ago
parent aec17d3e49
commit 08ce79466f
  1. 17
      cameraview/src/main/gles/com/otaliastudios/cameraview/AudioMediaEncoder.java
  2. 1
      cameraview/src/main/gles/com/otaliastudios/cameraview/EglBaseSurface.java
  3. 1
      cameraview/src/main/gles/com/otaliastudios/cameraview/EglCore.java
  4. 28
      cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoder.java
  5. 9
      cameraview/src/main/gles/com/otaliastudios/cameraview/MediaEncoderEngine.java
  6. 9
      cameraview/src/main/gles/com/otaliastudios/cameraview/TextureMediaEncoder.java

@ -115,13 +115,13 @@ class AudioMediaEncoder extends MediaEncoder {
// set audio data to encoder // set audio data to encoder
buffer.position(readBytes); buffer.position(readBytes);
buffer.flip(); buffer.flip();
encode(buffer, readBytes, getPresentationTime()); encode(buffer, readBytes, getInputPresentationTimeUs());
drain(false); drain(false);
} }
} }
// This will signal the endOfStream. // This will signal the endOfStream.
// Can't use drain(true); it is only available when writing to the codec InputSurface. // Can't use drain(true); it is only available when writing to the codec InputSurface.
encode(null, 0, getPresentationTime()); encode(null, 0, getInputPresentationTimeUs());
drain(false); drain(false);
mAudioRecord.stop(); mAudioRecord.stop();
mAudioRecord.release(); mAudioRecord.release();
@ -136,4 +136,17 @@ class AudioMediaEncoder extends MediaEncoder {
int getBitRate() { int getBitRate() {
return mConfig.bitRate; return mConfig.bitRate;
} }
private long mLastInputPresentationTimeUs = Long.MIN_VALUE;
// presentationTimeUs should be monotonic
// otherwise muxer fail to write
private long getInputPresentationTimeUs() {
long result = System.nanoTime() / 1000L;
if (result < mLastInputPresentationTimeUs) {
result = (mLastInputPresentationTimeUs - result) + result;
}
mLastInputPresentationTimeUs = result;
return result;
}
} }

@ -151,6 +151,7 @@ class EglBaseSurface extends EglElement {
/** /**
* Sends the presentation time stamp to EGL. * Sends the presentation time stamp to EGL.
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
* *
* @param nsecs Timestamp, in nanoseconds. * @param nsecs Timestamp, in nanoseconds.
*/ */

@ -314,6 +314,7 @@ final class EglCore {
/** /**
* Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
*/ */
public void setPresentationTime(EGLSurface eglSurface, long nsecs) { public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);

@ -155,17 +155,19 @@ abstract class MediaEncoder {
// 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);
mController.write(mTrackIndex, encodedData, mBufferInfo); if (mStartPresentationTimeUs == Long.MIN_VALUE) {
mLastPresentationTime = mBufferInfo.presentationTimeUs; mStartPresentationTimeUs = mBufferInfo.presentationTimeUs;
if (mStartPresentationTime == 0) {
mStartPresentationTime = mLastPresentationTime;
} }
mBufferInfo.presentationTimeUs = mBufferInfo.presentationTimeUs - mStartPresentationTimeUs;
if (mBufferInfo.presentationTimeUs == 0) mBufferInfo.presentationTimeUs = 1; // Read somewhere in Grafika that 0 should not be used
mController.write(mTrackIndex, encodedData, mBufferInfo);
mLastPresentationTimeUs = mBufferInfo.presentationTimeUs;
} }
mMediaCodec.releaseOutputBuffer(encoderStatus, false); mMediaCodec.releaseOutputBuffer(encoderStatus, false);
if (!mMaxLengthReached) { if (!mMaxLengthReached) {
if (mLastPresentationTime / 1000 - mStartPresentationTime / 1000 > mMaxLengthMillis) { if (mLastPresentationTimeUs - mStartPresentationTimeUs > mMaxLengthMillis * 1000) {
mMaxLengthReached = true; mMaxLengthReached = true;
// Log.e("MediaEncoder", this.getClass().getSimpleName() + " requested stop at " + (mLastPresentationTime * 1000 * 1000)); // Log.e("MediaEncoder", this.getClass().getSimpleName() + " requested stop at " + (mLastPresentationTimeUs * 1000 * 1000));
mController.requestStop(); mController.requestStop();
break; break;
} }
@ -178,18 +180,8 @@ abstract class MediaEncoder {
} }
} }
private long mStartPresentationTime = 0; private long mStartPresentationTimeUs = Long.MIN_VALUE;
private long mLastPresentationTime = 0; private long mLastPresentationTimeUs = 0;
long getPresentationTime() {
long result = System.nanoTime() / 1000L;
// presentationTimeUs should be monotonic
// otherwise muxer fail to write
if (result < mLastPresentationTime) {
result = (mLastPresentationTime - result) + result;
}
return result;
}
abstract int getBitRate(); abstract int getBitRate();
} }

@ -12,11 +12,13 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class MediaEncoderEngine { class MediaEncoderEngine {
private final static String TAG = MediaEncoder.class.getSimpleName(); private final static String TAG = MediaEncoderEngine.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG); private final static CameraLogger LOG = CameraLogger.create(TAG);
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@ -93,6 +95,7 @@ class MediaEncoderEngine {
throw new IllegalStateException("Trying to start but muxer started already"); throw new IllegalStateException("Trying to start but muxer started already");
} }
int track = mMediaMuxer.addTrack(format); int track = mMediaMuxer.addTrack(format);
LOG.w("Controller:", "Assigned track", track, "to format", format.getString(MediaFormat.KEY_MIME));
mMediaMuxerStartCount++; mMediaMuxerStartCount++;
if (mMediaMuxerStartCount == mEncoders.size()) { if (mMediaMuxerStartCount == mEncoders.size()) {
mMediaMuxer.start(); mMediaMuxer.start();
@ -113,6 +116,10 @@ class MediaEncoderEngine {
if (!mMediaMuxerStarted) { if (!mMediaMuxerStarted) {
throw new IllegalStateException("Trying to write before muxer started"); throw new IllegalStateException("Trying to write before muxer started");
} }
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(info.presentationTimeUs / 1000);
LOG.e("Writing for track", track, ". Presentation:",
calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND) + ":" + calendar.get(Calendar.MILLISECOND));
mMediaMuxer.writeSampleData(track, encodedData, info); mMediaMuxer.writeSampleData(track, encodedData, info);
} }

@ -17,6 +17,9 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
static class Frame { static class Frame {
float[] transform; float[] transform;
// Nanoseconds, in no meaningful time-base. Should be for offsets only.
// Typically coming from SurfaceTexture.getTimestamp().
long timestamp; long timestamp;
} }
static class Config extends VideoMediaEncoder.Config { static class Config extends VideoMediaEncoder.Config {
@ -99,14 +102,16 @@ class TextureMediaEncoder extends VideoMediaEncoder<TextureMediaEncoder.Config>
if (mFrameNum < 0) return; if (mFrameNum < 0) return;
mFrameNum++; mFrameNum++;
// No idea what this does. Seems to leave the timestamp unaffected, but
// will leave it here just in case.
int arg1 = (int) (frame.timestamp >> 32); int arg1 = (int) (frame.timestamp >> 32);
int arg2 = (int) frame.timestamp; int arg2 = (int) frame.timestamp;
long timestamp = (((long) arg1) << 32) | (((long) arg2) & 0xffffffffL); long timestamp = (((long) arg1) << 32) | (((long) arg2) & 0xffffffffL);
float[] transform = frame.transform; LOG.v("Incoming frame timestamp:", 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 scaleX = mConfig.scaleX; float scaleX = mConfig.scaleX;
float scaleY = mConfig.scaleY; float scaleY = mConfig.scaleY;
float scaleTranslX = (1F - scaleX) / 2F; float scaleTranslX = (1F - scaleX) / 2F;

Loading…
Cancel
Save