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