From c1604923efb199d96ac78f68d5a211edebc404a5 Mon Sep 17 00:00:00 2001 From: Pannag Sanketi Date: Sun, 16 Oct 2011 20:52:57 -0700 Subject: [PATCH] Add timeLapse support in MediaEncoderFilter For recording timelapsed videos with effects. Bug: 5452088 Change-Id: Ia99ca0af96bcd1da8a5f6dd4d2d46dde39ca9660 --- .../videosink/java/MediaEncoderFilter.java | 73 ++++++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/mca/filterpacks/videosink/java/MediaEncoderFilter.java b/mca/filterpacks/videosink/java/MediaEncoderFilter.java index 9feb6226..b0dbfd08 100644 --- a/mca/filterpacks/videosink/java/MediaEncoderFilter.java +++ b/mca/filterpacks/videosink/java/MediaEncoderFilter.java @@ -143,12 +143,18 @@ public class MediaEncoderFilter extends Filter { @GenerateFieldPort(name = "inputRegion", hasDefault = true) private Quad mSourceRegion; - /** Sets the maximum filesize (in bytes) of the recording session. + /** The maximum filesize (in bytes) of the recording session. * By default, it will be 0 and will be passed on to the MediaRecorder * If the limit is zero or negative, MediaRecorder will disable the limit*/ @GenerateFieldPort(name = "maxFileSize", hasDefault = true) private long mMaxFileSize = 0; + /** TimeLapse Interval between frames. + * By default, it will be 0. Whether the recording is timelapsed + * is inferred based on its value being greater than 0 */ + @GenerateFieldPort(name = "timelapseRecordingIntervalUs", hasDefault = true) + private long mTimeBetweenTimeLapseFrameCaptureUs = 0; + // End of user visible parameters private static final int NO_AUDIO_SOURCE = -1; @@ -158,6 +164,12 @@ public class MediaEncoderFilter extends Filter { private GLFrame mScreen; private boolean mRecordingActive = false; + private long mTimestampNs = 0; + private long mLastTimeLapseFrameRealTimestampNs = 0; + private int mNumFramesEncoded = 0; + // Used to indicate whether recording is timelapsed. + // Inferred based on (mTimeBetweenTimeLapseFrameCaptureUs > 0) + private boolean mCaptureTimeLapse = false; private boolean mLogVerbose; private static final String TAG = "MediaEncoderFilter"; @@ -218,13 +230,15 @@ public class MediaEncoderFilter extends Filter { // These have to be in certain order as per the MediaRecorder // documentation private void updateMediaRecorderParams() { + mCaptureTimeLapse = mTimeBetweenTimeLapseFrameCaptureUs > 0; final int GRALLOC_BUFFER = 2; mMediaRecorder.setVideoSource(GRALLOC_BUFFER); - if (mAudioSource != NO_AUDIO_SOURCE) { + if (!mCaptureTimeLapse && (mAudioSource != NO_AUDIO_SOURCE)) { mMediaRecorder.setAudioSource(mAudioSource); } if (mProfile != null) { mMediaRecorder.setProfile(mProfile); + mFps = mProfile.videoFrameRate; } else { mMediaRecorder.setOutputFormat(mOutputFormat); mMediaRecorder.setVideoEncoder(mVideoEncoder); @@ -308,13 +322,51 @@ public class MediaEncoderFilter extends Filter { // register the surface. The native window handle needed to create // the surface is initiated in start() mMediaRecorder.start(); - Log.v(TAG, "ME Filter: Open: registering surface from Mediarecorder"); + if (mLogVerbose) Log.v(TAG, "Open: registering surface from Mediarecorder"); mSurfaceId = context.getGLEnvironment(). registerSurfaceFromMediaRecorder(mMediaRecorder); - + mNumFramesEncoded = 0; mRecordingActive = true; } + public boolean skipFrameAndModifyTimestamp(long timestampNs) { + // first frame- encode. Don't skip + if (mNumFramesEncoded == 0) { + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = timestampNs; + if (mLogVerbose) Log.v(TAG, "timelapse: FIRST frame, last real t= " + + mLastTimeLapseFrameRealTimestampNs + + ", setting t = " + mTimestampNs ); + return false; + } + + // Workaround to bypass the first 2 input frames for skipping. + // The first 2 output frames from the encoder are: decoder specific info and + // the compressed video frame data for the first input video frame. + if (mNumFramesEncoded >= 2 && timestampNs < + (mLastTimeLapseFrameRealTimestampNs + 1000L * mTimeBetweenTimeLapseFrameCaptureUs)) { + // If 2 frames have been already encoded, + // Skip all frames from last encoded frame until + // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. + if (mLogVerbose) Log.v(TAG, "timelapse: skipping intermediate frame"); + return true; + } else { + // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: + // - Reset mLastTimeLapseFrameRealTimestampNs to current time. + // - Artificially modify timestampNs to be one frame time (1/framerate) ahead + // of the last encoded frame's time stamp. + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, Timestamp t = " + timestampNs + + ", last real t= " + mLastTimeLapseFrameRealTimestampNs + + ", interval = " + mTimeBetweenTimeLapseFrameCaptureUs); + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = mTimestampNs + (1000000000L / (long)mFps); + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, setting t = " + + mTimestampNs + ", delta t = " + (1000000000L / (long)mFps) + + ", fps = " + mFps ); + return false; + } + } + @Override public void process(FilterContext context) { if (mLogVerbose) Log.v(TAG, "Starting frame processing"); @@ -334,6 +386,14 @@ public class MediaEncoderFilter extends Filter { if (!mRecordingActive) return; + if (mCaptureTimeLapse) { + if (skipFrameAndModifyTimestamp(input.getTimestamp())) { + return; + } + } else { + mTimestampNs = input.getTimestamp(); + } + // Activate our surface glEnv.activateSurfaceWithId(mSurfaceId); @@ -341,15 +401,18 @@ public class MediaEncoderFilter extends Filter { mProgram.process(input, mScreen); // Set timestamp from input - glEnv.setSurfaceTimestamp(input.getTimestamp()); + glEnv.setSurfaceTimestamp(mTimestampNs); // And swap buffers glEnv.swapBuffers(); + mNumFramesEncoded++; + if (mLogVerbose) Log.v(TAG, "numFramesEncoded = " + mNumFramesEncoded); } private void stopRecording(FilterContext context) { if (mLogVerbose) Log.v(TAG, "Stopping recording"); mRecordingActive = false; + mNumFramesEncoded = 0; GLEnvironment glEnv = context.getGLEnvironment(); // The following call will switch the surface_id to 0 // (thus, calling eglMakeCurrent on surface with id 0) and -- 2.11.0