165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn/* 265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Copyright (C) 2011 The Android Open Source Project 365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Licensed under the Apache License, Version 2.0 (the "License"); 565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * you may not use this file except in compliance with the License. 665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * You may obtain a copy of the License at 765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * http://www.apache.org/licenses/LICENSE-2.0 965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 1065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Unless required by applicable law or agreed to in writing, software 1165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * distributed under the License is distributed on an "AS IS" BASIS, 1265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * See the License for the specific language governing permissions and 1465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * limitations under the License. 1565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 1665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 1765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 1865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennpackage android.filterpacks.videosink; 1965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 2065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.Filter; 2165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.FilterContext; 2265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.Frame; 2365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.FrameFormat; 2465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.GenerateFieldPort; 2565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.GLFrame; 2665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.MutableFrameFormat; 2765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.ShaderProgram; 2865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.format.ImageFormat; 2965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.geometry.Point; 3065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.geometry.Quad; 3165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.media.MediaRecorder; 3265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.media.CamcorderProfile; 3365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.filterfw.core.GLEnvironment; 3465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 3565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport java.io.IOException; 3665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport java.io.FileDescriptor; 3765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 3865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennimport android.util.Log; 3965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 4065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn/** @hide */ 4165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Rennpublic class MediaEncoderFilter extends Filter { 4265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 4365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** User-visible parameters */ 4465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 4565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Recording state. When set to false, recording will stop, or will not 4665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * start if not yet running the graph. Instead, frames are simply ignored. 4765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * When switched back to true, recording will restart. This allows a single 4865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * graph to both provide preview and to record video. If this is false, 4965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * recording settings can be updated while the graph is running. 5065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 5165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "recording", hasDefault = true) 5265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private boolean mRecording = true; 5365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 5465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Filename to save the output. */ 5565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "outputFile", hasDefault = true) 5665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private String mOutputFile = new String("/sdcard/MediaEncoderOut.mp4"); 5765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 5865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** File Descriptor to save the output. */ 5965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "outputFileDescriptor", hasDefault = true) 6065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private FileDescriptor mFd = null; 6165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 6265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Input audio source. If not set, no audio will be recorded. 6365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Select from the values in MediaRecorder.AudioSource 6465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 6565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "audioSource", hasDefault = true) 6665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mAudioSource = NO_AUDIO_SOURCE; 6765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 6865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Media recorder info listener, which needs to implement 6965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * MediaRecorder.OnInfoListener. Set this to receive notifications about 7065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * recording events. 7165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 7265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "infoListener", hasDefault = true) 7365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private MediaRecorder.OnInfoListener mInfoListener = null; 7465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 7565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Media recorder error listener, which needs to implement 7665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * MediaRecorder.OnErrorListener. Set this to receive notifications about 7765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * recording errors. 7865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 7965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "errorListener", hasDefault = true) 8065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private MediaRecorder.OnErrorListener mErrorListener = null; 8165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 8265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Media recording done callback, which needs to implement OnRecordingDoneListener. 8365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Set this to finalize media upon completion of media recording. 8465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 8565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "recordingDoneListener", hasDefault = true) 8665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private OnRecordingDoneListener mRecordingDoneListener = null; 8765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 8865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Orientation hint. Used for indicating proper video playback orientation. 8965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Units are in degrees of clockwise rotation, valid values are (0, 90, 180, 9065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * 270). 9165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 9265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "orientationHint", hasDefault = true) 9365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mOrientationHint = 0; 9465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 9565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Camcorder profile to use. Select from the profiles available in 9665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * android.media.CamcorderProfile. If this field is set, it overrides 9765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * settings to width, height, framerate, outputFormat, and videoEncoder. 9865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 9965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "recordingProfile", hasDefault = true) 10065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private CamcorderProfile mProfile = null; 10165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 10265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Frame width to be encoded, defaults to 320. 10365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Actual received frame size has to match this */ 10465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "width", hasDefault = true) 10558acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi private int mWidth = 0; 10665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 10765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Frame height to to be encoded, defaults to 240. 10865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Actual received frame size has to match */ 10965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "height", hasDefault = true) 11058acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi private int mHeight = 0; 11165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 11265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Stream framerate to encode the frames at. 11365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * By default, frames are encoded at 30 FPS*/ 11465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "framerate", hasDefault = true) 11565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mFps = 30; 11665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 11765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** The output format to encode the frames in. 11865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Choose an output format from the options in 11965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * android.media.MediaRecorder.OutputFormat */ 12065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "outputFormat", hasDefault = true) 12165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mOutputFormat = MediaRecorder.OutputFormat.MPEG_4; 12265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 12365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** The videoencoder to encode the frames with. 12465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * Choose a videoencoder from the options in 12565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * android.media.MediaRecorder.VideoEncoder */ 12665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "videoEncoder", hasDefault = true) 12765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mVideoEncoder = MediaRecorder.VideoEncoder.H264; 12865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 12965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** The input region to read from the frame. The corners of this quad are 13065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * mapped to the output rectangle. The input frame ranges from (0,0)-(1,1), 13165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * top-left to bottom-right. The corners of the quad are specified in the 13265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * order bottom-left, bottom-right, top-left, top-right. 13365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn */ 13465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "inputRegion", hasDefault = true) 13565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private Quad mSourceRegion; 13665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 13765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** The maximum filesize (in bytes) of the recording session. 13865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * By default, it will be 0 and will be passed on to the MediaRecorder. 13965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * If the limit is zero or negative, MediaRecorder will disable the limit*/ 14065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "maxFileSize", hasDefault = true) 14165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private long mMaxFileSize = 0; 14265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 14365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** The maximum duration (in milliseconds) of the recording session. 14465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * By default, it will be 0 and will be passed on to the MediaRecorder. 14565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * If the limit is zero or negative, MediaRecorder will record indefinitely*/ 14665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "maxDurationMs", hasDefault = true) 14765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mMaxDurationMs = 0; 14865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 14965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** TimeLapse Interval between frames. 15065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * By default, it will be 0. Whether the recording is timelapsed 15165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn * is inferred based on its value being greater than 0 */ 15265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @GenerateFieldPort(name = "timelapseRecordingIntervalUs", hasDefault = true) 15365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private long mTimeBetweenTimeLapseFrameCaptureUs = 0; 15465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 15565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // End of user visible parameters 15665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 15765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private static final int NO_AUDIO_SOURCE = -1; 15865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 15965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mSurfaceId; 16065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private ShaderProgram mProgram; 16165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private GLFrame mScreen; 16265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 16365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private boolean mRecordingActive = false; 16465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private long mTimestampNs = 0; 16565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private long mLastTimeLapseFrameRealTimestampNs = 0; 16665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private int mNumFramesEncoded = 0; 16765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Used to indicate whether recording is timelapsed. 16865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Inferred based on (mTimeBetweenTimeLapseFrameCaptureUs > 0) 16965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private boolean mCaptureTimeLapse = false; 17065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 17165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private boolean mLogVerbose; 17265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private static final String TAG = "MediaEncoderFilter"; 17365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 17465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Our hook to the encoder 17565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private MediaRecorder mMediaRecorder; 17665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 17765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn /** Callback to be called when media recording completes. */ 17865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 17965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public interface OnRecordingDoneListener { 18065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void onRecordingDone(); 18165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 18265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 18365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public MediaEncoderFilter(String name) { 18465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn super(name); 18565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Point bl = new Point(0, 0); 18665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Point br = new Point(1, 0); 18765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Point tl = new Point(0, 1); 18865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Point tr = new Point(1, 1); 18965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mSourceRegion = new Quad(bl, br, tl, tr); 19065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); 19165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 19265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 19365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 19465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void setupPorts() { 19565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Add input port- will accept RGBA GLFrames 19665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn addMaskedInputPort("videoframe", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, 19765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn FrameFormat.TARGET_GPU)); 19865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 19965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 20065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 20165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void fieldPortValueUpdated(String name, FilterContext context) { 20265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Port " + name + " has been updated"); 20365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (name.equals("recording")) return; 20465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (name.equals("inputRegion")) { 20565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (isOpen()) updateSourceRegion(); 20665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return; 20765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 20865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // TODO: Not sure if it is possible to update the maxFileSize 20965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // when the recording is going on. For now, not doing that. 21065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (isOpen() && mRecordingActive) { 21165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn throw new RuntimeException("Cannot change recording parameters" 21265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + " when the filter is recording!"); 21365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 21465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 21565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 21665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private void updateSourceRegion() { 21765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Flip source quad to map to OpenGL origin 21865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Quad flippedRegion = new Quad(); 21965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn flippedRegion.p0 = mSourceRegion.p2; 22065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn flippedRegion.p1 = mSourceRegion.p3; 22165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn flippedRegion.p2 = mSourceRegion.p0; 22265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn flippedRegion.p3 = mSourceRegion.p1; 22365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mProgram.setSourceRegion(flippedRegion); 22465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 22565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 22665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // update the MediaRecorderParams based on the variables. 22765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // These have to be in certain order as per the MediaRecorder 22865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // documentation 22965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private void updateMediaRecorderParams() { 23065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mCaptureTimeLapse = mTimeBetweenTimeLapseFrameCaptureUs > 0; 23165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn final int GRALLOC_BUFFER = 2; 23265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setVideoSource(GRALLOC_BUFFER); 23365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (!mCaptureTimeLapse && (mAudioSource != NO_AUDIO_SOURCE)) { 23465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setAudioSource(mAudioSource); 23565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 23665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mProfile != null) { 23765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setProfile(mProfile); 23865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mFps = mProfile.videoFrameRate; 23958acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi // If width and height are set larger than 0, then those 24058acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi // overwrite the ones in the profile. 24158acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi if (mWidth > 0 && mHeight > 0) { 24258acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi mMediaRecorder.setVideoSize(mWidth, mHeight); 24358acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi } 24465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } else { 24565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOutputFormat(mOutputFormat); 24665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setVideoEncoder(mVideoEncoder); 24765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setVideoSize(mWidth, mHeight); 24865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setVideoFrameRate(mFps); 24965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 25065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOrientationHint(mOrientationHint); 25165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOnInfoListener(mInfoListener); 25265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOnErrorListener(mErrorListener); 25365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mFd != null) { 25465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOutputFile(mFd); 25565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } else { 25665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setOutputFile(mOutputFile); 25765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 25865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn try { 25965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setMaxFileSize(mMaxFileSize); 26065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } catch (Exception e) { 26165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Following the logic in VideoCamera.java (in Camera app) 26265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // We are going to ignore failure of setMaxFileSize here, as 26365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // a) The composer selected may simply not support it, or 26465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // b) The underlying media framework may not handle 64-bit range 26565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // on the size restriction. 26665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Log.w(TAG, "Setting maxFileSize on MediaRecorder unsuccessful! " 26765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + e.getMessage()); 26865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 26965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.setMaxDuration(mMaxDurationMs); 27065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 27165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 27265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 27365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void prepare(FilterContext context) { 27465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Preparing"); 27565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 27665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mProgram = ShaderProgram.createIdentity(context); 27765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 27865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mRecordingActive = false; 27965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 28065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 28165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 28265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void open(FilterContext context) { 28365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Opening"); 28465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn updateSourceRegion(); 28565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mRecording) startRecording(context); 28665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 28765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 28865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private void startRecording(FilterContext context) { 28965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Starting recording"); 29065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 29165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Create a frame representing the screen 29265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn MutableFrameFormat screenFormat = new MutableFrameFormat( 29365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn FrameFormat.TYPE_BYTE, FrameFormat.TARGET_GPU); 29465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn screenFormat.setBytesPerSample(4); 29565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 29665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn int width, height; 29758acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi boolean widthHeightSpecified = mWidth > 0 && mHeight > 0; 29858acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi // If width and height are specified, then use those instead 29958acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi // of that in the profile. 30058acf44b9f16d5154e6d3a0e5f7a7a7a3c7b423fPannag Sanketi if (mProfile != null && !widthHeightSpecified) { 30165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn width = mProfile.videoFrameWidth; 30265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn height = mProfile.videoFrameHeight; 30365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } else { 30465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn width = mWidth; 30565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn height = mHeight; 30665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 30765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn screenFormat.setDimensions(width, height); 30865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mScreen = (GLFrame)context.getFrameManager().newBoundFrame( 30965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn screenFormat, GLFrame.EXISTING_FBO_BINDING, 0); 31065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 31165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Initialize the media recorder 31265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 31365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder = new MediaRecorder(); 31465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn updateMediaRecorderParams(); 31565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 31665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn try { 31765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.prepare(); 31865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } catch (IllegalStateException e) { 31965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn throw e; 32065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } catch (IOException e) { 32165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn throw new RuntimeException("IOException in" 32265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + "MediaRecorder.prepare()!", e); 32365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } catch (Exception e) { 32465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn throw new RuntimeException("Unknown Exception in" 32565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + "MediaRecorder.prepare()!", e); 32665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 32765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Make sure start() is called before trying to 32865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // register the surface. The native window handle needed to create 32965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // the surface is initiated in start() 33065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.start(); 33165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Open: registering surface from Mediarecorder"); 33265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mSurfaceId = context.getGLEnvironment(). 33365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn registerSurfaceFromMediaRecorder(mMediaRecorder); 33465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mNumFramesEncoded = 0; 33565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mRecordingActive = true; 33665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 33765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 33865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public boolean skipFrameAndModifyTimestamp(long timestampNs) { 33965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // first frame- encode. Don't skip 34065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mNumFramesEncoded == 0) { 34165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mLastTimeLapseFrameRealTimestampNs = timestampNs; 34265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mTimestampNs = timestampNs; 34365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "timelapse: FIRST frame, last real t= " 34465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + mLastTimeLapseFrameRealTimestampNs + 34565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ", setting t = " + mTimestampNs ); 34665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return false; 34765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 34865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 34965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Workaround to bypass the first 2 input frames for skipping. 35065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // The first 2 output frames from the encoder are: decoder specific info and 35165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // the compressed video frame data for the first input video frame. 35265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mNumFramesEncoded >= 2 && timestampNs < 35365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn (mLastTimeLapseFrameRealTimestampNs + 1000L * mTimeBetweenTimeLapseFrameCaptureUs)) { 35465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // If 2 frames have been already encoded, 35565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Skip all frames from last encoded frame until 35665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. 35765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "timelapse: skipping intermediate frame"); 35865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return true; 35965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } else { 36065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: 36165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // - Reset mLastTimeLapseFrameRealTimestampNs to current time. 36265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // - Artificially modify timestampNs to be one frame time (1/framerate) ahead 36365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // of the last encoded frame's time stamp. 36465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, Timestamp t = " + timestampNs + 36565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ", last real t= " + mLastTimeLapseFrameRealTimestampNs + 36665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ", interval = " + mTimeBetweenTimeLapseFrameCaptureUs); 36765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mLastTimeLapseFrameRealTimestampNs = timestampNs; 36865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mTimestampNs = mTimestampNs + (1000000000L / (long)mFps); 36965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, setting t = " 37065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn + mTimestampNs + ", delta t = " + (1000000000L / (long)mFps) + 37165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn ", fps = " + mFps ); 37265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return false; 37365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 37465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 37565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 37665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 37765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void process(FilterContext context) { 37865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn GLEnvironment glEnv = context.getGLEnvironment(); 37965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Get input frame 38065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn Frame input = pullInput("videoframe"); 38165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 38265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Check if recording needs to start 38365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (!mRecordingActive && mRecording) { 38465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn startRecording(context); 38565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 38665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Check if recording needs to stop 38765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mRecordingActive && !mRecording) { 38865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn stopRecording(context); 38965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 39065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 39165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (!mRecordingActive) return; 39265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 39365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mCaptureTimeLapse) { 39465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (skipFrameAndModifyTimestamp(input.getTimestamp())) { 39565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn return; 39665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 39765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } else { 39865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mTimestampNs = input.getTimestamp(); 39965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 40065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 40165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Activate our surface 40265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn glEnv.activateSurfaceWithId(mSurfaceId); 40365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 40465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Process 40565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mProgram.process(input, mScreen); 40665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 40765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Set timestamp from input 40865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn glEnv.setSurfaceTimestamp(mTimestampNs); 40965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // And swap buffers 41065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn glEnv.swapBuffers(); 41165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mNumFramesEncoded++; 41265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 41365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 41465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn private void stopRecording(FilterContext context) { 41565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Stopping recording"); 41665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 41765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mRecordingActive = false; 41865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mNumFramesEncoded = 0; 41965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn GLEnvironment glEnv = context.getGLEnvironment(); 42065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // The following call will switch the surface_id to 0 42165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // (thus, calling eglMakeCurrent on surface with id 0) and 42265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // then call eglDestroy on the surface. Hence, this will 42365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // call disconnect the SurfaceMediaSource, which is needed to 42465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // be called before calling Stop on the mediarecorder 42565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, String.format("Unregistering surface %d", mSurfaceId)); 42665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn glEnv.unregisterSurfaceId(mSurfaceId); 42765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn try { 42865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.stop(); 42965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } catch (RuntimeException e) { 43065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn throw new MediaRecorderStopException("MediaRecorder.stop() failed!", e); 43165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 43265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.release(); 43365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder = null; 43465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 43565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mScreen.release(); 43665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mScreen = null; 43765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 43865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Use an EffectsRecorder callback to forward a media finalization 43965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // call so that it creates the video thumbnail, and whatever else needs 44065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // to be done to finalize media. 44165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mRecordingDoneListener != null) { 44265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mRecordingDoneListener.onRecordingDone(); 44365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 44465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 44565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 44665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 44765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void close(FilterContext context) { 44865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mLogVerbose) Log.v(TAG, "Closing"); 44965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mRecordingActive) stopRecording(context); 45065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 45165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 45265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn @Override 45365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn public void tearDown(FilterContext context) { 45465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // Release all the resources associated with the MediaRecorder 45565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn // and GLFrame members 45665953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mMediaRecorder != null) { 45765953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mMediaRecorder.release(); 45865953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 45965953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn if (mScreen != null) { 46065953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn mScreen.release(); 46165953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 46265953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 46365953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn } 46465953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn 46565953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9Marius Renn} 466