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