19d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He/*
29d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * Copyright (C) 2013 The Android Open Source Project
39d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He *
49d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * Licensed under the Apache License, Version 2.0 (the "License");
59d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * you may not use this file except in compliance with the License.
69d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * You may obtain a copy of the License at
79d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He *
89d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He *      http://www.apache.org/licenses/LICENSE-2.0
99d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He *
109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * Unless required by applicable law or agreed to in writing, software
119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * distributed under the License is distributed on an "AS IS" BASIS,
129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * See the License for the specific language governing permissions and
149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * limitations under the License.
159d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He */
169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
177c5f2935442e974ce30158e35d142f7e2c3ee1a2Eino-Ville Talvalapackage com.android.testingcamera2.v1;
189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
19f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shihimport android.content.Context;
209d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.hardware.camera2.CaptureRequest;
21e82937211409e869f32c8398ee16ce6bb77bfce6Igor Murashkinimport android.util.Size;
229d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.media.MediaCodec;
239d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.media.MediaCodecInfo;
249d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.media.MediaFormat;
259d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.media.MediaMuxer;
26be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhangimport android.media.MediaRecorder;
27f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shihimport android.media.MediaScannerConnection;
289d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.os.Environment;
299d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.util.Log;
300b631a014c90c9053a8e17be141351abab66d30eEino-Ville Talvalaimport android.util.Size;
319d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport android.view.Surface;
329d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
339d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.io.File;
349d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.io.IOException;
359d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.nio.ByteBuffer;
369d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.text.SimpleDateFormat;
379d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.util.Date;
389d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Heimport java.util.List;
399d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
409d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He/**
419d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * Camera video recording class. It takes frames produced by camera and encoded
429d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * with either MediaCodec or MediaRecorder. MediaRecorder path is not
439d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He * implemented yet.
449d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He */
459d42c135c172cab66b7eca856cd578ed85d512f6Zhijun Hepublic class CameraRecordingStream {
469d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final String TAG = "CameraRecordingStream";
479d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
489d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int STREAM_STATE_IDLE = 0;
499d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int STREAM_STATE_CONFIGURED = 1;
509d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int STREAM_STATE_RECORDING = 2;
519d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int FRAME_RATE = 30; // 30fps
529d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int IFRAME_INTERVAL = 1; // 1 seconds between I-frames
539d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private static final int TIMEOUT_USEC = 10000; // Timeout value 10ms.
549d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    // Sync object to protect stream state access from multiple threads.
559d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private final Object mStateLock = new Object();
569d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
579d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private int mStreamState = STREAM_STATE_IDLE;
589d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private MediaCodec mEncoder;
599d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private Surface mRecordingSurface;
609d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private int mEncBitRate;
61be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    private int mOrientation;
629d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private MediaCodec.BufferInfo mBufferInfo;
639d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private MediaMuxer mMuxer;
649d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private int mTrackIndex = -1;
659d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private boolean mMuxerStarted;
669d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private boolean mUseMediaCodec = false;
679d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private Size mStreamSize = new Size(-1, -1);
68f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    private int mOutputFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
699d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private Thread mRecordingThread;
70be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    private MediaRecorder mMediaRecorder;
71f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    private String mOutputFile;
729d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
739d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    public CameraRecordingStream() {
749d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
759d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
769d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
779d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Configure stream with a size and encoder mode.
789d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     *
79f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih     * @param ctx Application context.
809d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param size Size of recording stream.
819d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param useMediaCodec The encoder for this stream to use, either MediaCodec
829d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * or MediaRecorder.
839d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param bitRate Bit rate the encoder takes.
84be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang     * @param orientation Recording orientation in degree (0,90,180,270)
85f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih     * @param outputFormat Output file format as listed in {@link MediaMuxer.OutputFormat}
869d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
87be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    public synchronized void configure(
88f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            Context ctx, Size size, boolean useMediaCodec, int bitRate, int orientation,
89f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            int outputFormat) {
909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (getStreamState() == STREAM_STATE_RECORDING) {
919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            throw new IllegalStateException(
929d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    "Stream can only be configured when stream is in IDLE state");
939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
949d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        boolean isConfigChanged =
969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                (!mStreamSize.equals(size)) ||
979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                (mUseMediaCodec != useMediaCodec) ||
98be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang                (mEncBitRate != bitRate) ||
99be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang                (mOrientation != orientation);
1009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1019d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mStreamSize = size;
1029d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mUseMediaCodec = useMediaCodec;
1039d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mEncBitRate = bitRate;
104be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        mOrientation = orientation;
105f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih        mOutputFormat = outputFormat;
1069d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1079d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (mUseMediaCodec) {
1089d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (getStreamState() == STREAM_STATE_CONFIGURED) {
1099d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                /**
1109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 * Stream is already configured, need release encoder and muxer
1119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 * first, then reconfigure only if configuration is changed.
1129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 */
1139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (!isConfigChanged) {
1149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    /**
1159d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * TODO: this is only the skeleton, it is tricky to
1169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * implement because muxer need reconfigure always. But
1179d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * muxer is closely coupled with MediaCodec for now because
1189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * muxer can only be started once format change callback is
1199d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * sent from mediacodec. We need decouple MediaCodec and
1209d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * Muxer for future.
1219d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     */
1229d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
1239d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                releaseEncoder();
124f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                releaseMuxer(ctx);
1259d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                configureMediaCodecEncoder();
1269d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
1279d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                configureMediaCodecEncoder();
1289d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
1299d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } else {
130be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            configureMediaRecorder();
1319d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
1329d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1339d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        setStreamState(STREAM_STATE_CONFIGURED);
1349d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
1359d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1369d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
1379d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Add the stream output surface to the target output surface list.
1389d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     *
1399d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param outputSurfaces The output surface list where the stream can
1409d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * add/remove its output surface.
1419d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param detach Detach the recording surface from the outputSurfaces.
1429d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
1439d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    public synchronized void onConfiguringOutputs(List<Surface> outputSurfaces,
1449d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            boolean detach) {
1459d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (detach) {
1469d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Can detach the surface in CONFIGURED and RECORDING state
1479d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (getStreamState() != STREAM_STATE_IDLE) {
1489d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                outputSurfaces.remove(mRecordingSurface);
1499d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
1509d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.w(TAG, "Can not detach surface when recording stream is in IDLE state");
1519d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
1529d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } else {
1539d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Can add surface only in CONFIGURED state.
1549d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (getStreamState() == STREAM_STATE_CONFIGURED) {
1559d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                outputSurfaces.add(mRecordingSurface);
1569d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
1579d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.w(TAG, "Can only add surface when recording stream is in CONFIGURED state");
1589d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
1599d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
1609d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
1619d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1629d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
1639d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Update capture request with configuration required for recording stream.
1649d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     *
165a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala     * @param requestBuilder Capture request builder that needs to be updated
166a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala     * for recording specific camera settings.
1679d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * @param detach Detach the recording surface from the capture request.
1689d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
169a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala    public synchronized void onConfiguringRequest(CaptureRequest.Builder requestBuilder,
170a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala            boolean detach) {
1719d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (detach) {
1729d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Can detach the surface in CONFIGURED and RECORDING state
1739d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (getStreamState() != STREAM_STATE_IDLE) {
174a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala                requestBuilder.removeTarget(mRecordingSurface);
1759d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
1769d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.w(TAG, "Can not detach surface when recording stream is in IDLE state");
1779d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
1789d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } else {
1799d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Can add surface only in CONFIGURED state.
1809d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (getStreamState() == STREAM_STATE_CONFIGURED) {
181a04e462a07854595122f19b1df9f19c78e5dc030Eino-Ville Talvala                requestBuilder.addTarget(mRecordingSurface);
1829d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
1839d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.w(TAG, "Can only add surface when recording stream is in CONFIGURED state");
1849d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
1859d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
1869d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
1879d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1889d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
1899d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Start recording stream. Calling start on an already started stream has no
1909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * effect.
1919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
1929d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    public synchronized void start() {
1939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (getStreamState() == STREAM_STATE_RECORDING) {
1949d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.w(TAG, "Recording stream is already started");
1959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            return;
1969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
1979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
1989d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (getStreamState() != STREAM_STATE_CONFIGURED) {
1999d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            throw new IllegalStateException("Recording stream is not configured yet");
2009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2019d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
202be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        setStreamState(STREAM_STATE_RECORDING);
2039d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (mUseMediaCodec) {
2049d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            startMediaCodecRecording();
2059d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } else {
206be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mMediaRecorder.start();
2079d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2089d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
2099d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
2119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * <p>
2129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Stop recording stream. Calling stop on an already stopped stream has no
2139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * effect. Producer(in this case, CameraDevice) should stop before this call
2149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * to avoid sending buffers to a stopped encoder.
2159d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * </p>
2169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * <p>
2179d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * TODO: We have to release encoder and muxer for MediaCodec mode because
2189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * encoder is closely coupled with muxer, and muxser can not be reused
2199d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * across different recording session(by design, you can not reset/restart
2209d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * it). To save the subsequent start recording time, we need avoid releasing
2219d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * encoder for future.
2229d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * </p>
223f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih     * @param ctx Application context.
2249d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
225f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    public synchronized void stop(Context ctx) {
2269d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (getStreamState() != STREAM_STATE_RECORDING) {
2279d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.w(TAG, "Recording stream is not started yet");
2289d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            return;
2299d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2309d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2319d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        setStreamState(STREAM_STATE_IDLE);
2329d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        Log.e(TAG, "setting camera to idle");
2339d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (mUseMediaCodec) {
2349d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Wait until recording thread stop
2359d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            try {
2369d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mRecordingThread.join();
2379d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } catch (InterruptedException e) {
2389d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                throw new RuntimeException("Stop recording failed", e);
2399d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
2409d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            // Drain encoder
2419d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            doMediaCodecEncoding(/* notifyEndOfStream */true);
2429d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            releaseEncoder();
243f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            releaseMuxer(ctx);
2449d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } else {
245be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            try {
246be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang                mMediaRecorder.stop();
247be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            } catch (RuntimeException e) {
248be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang                // this can happen if there were no frames received by recorder
249be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang                Log.e(TAG, "Could not create output file");
250be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            }
251be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            releaseMediaRecorder();
2529d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2539d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
2549d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2559d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
2569d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Starts MediaCodec mode recording.
2579d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
2589d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private void startMediaCodecRecording() {
2599d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        /**
2609d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * Start video recording asynchronously. we need a loop to handle output
2619d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * data for each frame.
2629d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         */
2639d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mRecordingThread = new Thread() {
2649d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            @Override
2659d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            public void run() {
2669d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (VERBOSE) {
2679d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    Log.v(TAG, "Recording thread starts");
2689d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
2699d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2709d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                while (getStreamState() == STREAM_STATE_RECORDING) {
2719d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    // Feed encoder output into the muxer until recording stops.
2729d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    doMediaCodecEncoding(/* notifyEndOfStream */false);
2739d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
2749d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (VERBOSE) {
2759d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    Log.v(TAG, "Recording thread completes");
2769d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
2779d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                return;
2789d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
2799d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        };
2809d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mRecordingThread.start();
2819d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
2829d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2839d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    // Thread-safe access to the stream state.
2849d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private synchronized void setStreamState(int state) {
2859d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        synchronized (mStateLock) {
2869d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (state < STREAM_STATE_IDLE) {
2879d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                throw new IllegalStateException("try to set an invalid state");
2889d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
2899d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mStreamState = state;
2909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
2929d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
2939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    // Thread-safe access to the stream state.
2949d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private int getStreamState() {
2959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        synchronized(mStateLock) {
2969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            return mStreamState;
2979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
2989d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
2999d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
3009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private void releaseEncoder() {
3019d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        // Release encoder
3029d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (VERBOSE) {
3039d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.v(TAG, "releasing encoder");
3049d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3059d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (mEncoder != null) {
3069d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mEncoder.stop();
3079d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mEncoder.release();
3089d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (mRecordingSurface != null) {
3099d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mRecordingSurface.release();
3109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
3119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mEncoder = null;
3129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
3149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
315f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    private void releaseMuxer(Context ctx) {
3169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (VERBOSE) {
3179d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.v(TAG, "releasing muxer");
3189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3199d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
3209d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (mMuxer != null) {
3219d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mMuxer.stop();
3229d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mMuxer.release();
3239d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mMuxer = null;
324f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            MediaScannerConnection.scanFile(ctx, new String [] { mOutputFile }, null, null);
3259d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3269d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
3279d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
328be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    private void releaseMediaRecorder() {
329be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        if (VERBOSE) {
330be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            Log.v(TAG, "releasing media recorder");
331be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        }
332be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang
333be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        if (mMediaRecorder != null) {
334be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mMediaRecorder.release();
335be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mMediaRecorder = null;
336be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        }
337be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang
338be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        if (mRecordingSurface != null) {
339be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mRecordingSurface.release();
340be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mRecordingSurface = null;
341be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        }
342be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    }
343be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang
344f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    private String getOutputMime() {
345f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih        switch (mOutputFormat) {
346f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            case MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4:
347f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                return "video/avc";
348f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
349f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            case MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM:
350f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                return "video/x-vnd.on2.vp8";
351f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
352f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            default:
353f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                throw new IllegalStateException("Configure with unrecognized format.");
354f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih        }
355f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    }
356f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
357f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    private String getOutputExtension() {
358f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih        switch (mOutputFormat) {
359f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            case MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4:
360f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                return ".mp4";
361f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
362f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            case MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM:
363f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                return ".webm";
364f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
365f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            default:
366f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                throw new IllegalStateException("Configure with unrecognized format.");
367f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih        }
368f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih    }
369f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih
3709d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private String getOutputMediaFileName() {
3719d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        String state = Environment.getExternalStorageState();
3729d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        // Check if external storage is mounted
3739d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (!Environment.MEDIA_MOUNTED.equals(state)) {
3749d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.e(TAG, "External storage is not mounted!");
3759d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            return null;
3769d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3779d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
3789d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
3799d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Environment.DIRECTORY_DCIM), "TestingCamera2");
3809d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        // Create the storage directory if it does not exist
3819d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (!mediaStorageDir.exists()) {
3829d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (!mediaStorageDir.mkdirs()) {
3839d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.e(TAG, "Failed to create directory " + mediaStorageDir.getPath()
3849d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        + " for pictures/video!");
3859d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                return null;
3869d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
3879d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
3889d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
3899d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        // Create a media file name
3909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
3919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        String mediaFileName = mediaStorageDir.getPath() + File.separator +
392f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                "VID_" + timeStamp + getOutputExtension();
3939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
394be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        Log.v(TAG, "Recording file name: " + mediaFileName);
3959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        return mediaFileName;
3969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
3979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
3989d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
3999d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Configures encoder and muxer state, and prepares the input Surface.
4009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Initializes mEncoder, mMuxer, mRecordingSurface, mBufferInfo,
4019d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * mTrackIndex, and mMuxerStarted.
4029d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
4039d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private void configureMediaCodecEncoder() {
4049d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mBufferInfo = new MediaCodec.BufferInfo();
4059d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        MediaFormat format =
406f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                MediaFormat.createVideoFormat(getOutputMime(),
4079d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        mStreamSize.getWidth(), mStreamSize.getHeight());
4089d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        /**
4099d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * Set encoding properties. Failing to specify some of these can cause
4109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * the MediaCodec configure() call to throw an exception.
4119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         */
4129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
4139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
4149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        format.setInteger(MediaFormat.KEY_BIT_RATE, mEncBitRate);
4159d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
4169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
4179d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        Log.i(TAG, "configure video encoding format: " + format);
4189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
4199d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        // Create/configure a MediaCodec encoder.
420dae13b0bd71a56ac1d40c46622e9b00fd1ac32f5Andy Hung        try {
421f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            mEncoder = MediaCodec.createEncoderByType(getOutputMime());
422dae13b0bd71a56ac1d40c46622e9b00fd1ac32f5Andy Hung        } catch (IOException ioe) {
423dae13b0bd71a56ac1d40c46622e9b00fd1ac32f5Andy Hung            throw new IllegalStateException(
424f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih                    "failed to create " + getOutputMime() + " encoder", ioe);
425dae13b0bd71a56ac1d40c46622e9b00fd1ac32f5Andy Hung        }
4269d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
4279d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mRecordingSurface = mEncoder.createInputSurface();
4289d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mEncoder.start();
4299d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
4309d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        String outputFileName = getOutputMediaFileName();
4319d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (outputFileName == null) {
4329d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            throw new IllegalStateException("Failed to get video output file");
4339d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
4349d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
4359d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        /**
4369d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * Create a MediaMuxer. We can't add the video track and start() the
4379d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         * muxer until the encoder starts and notifies the new media format.
4389d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He         */
4399d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        try {
440f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            mOutputFile = outputFileName;
441f3dc34a8f0ae74a9e1cf153aff7bcf31c92bc194Robert Shih            mMuxer = new MediaMuxer(mOutputFile, mOutputFormat);
442be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mMuxer.setOrientationHint(mOrientation);
4439d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } catch (IOException ioe) {
4449d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            throw new IllegalStateException("MediaMuxer creation failed", ioe);
4459d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
4469d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        mMuxerStarted = false;
4479d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
4489d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
449be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    private void configureMediaRecorder() {
450be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        String outputFileName = getOutputMediaFileName();
451be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        if (outputFileName == null) {
452be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            throw new IllegalStateException("Failed to get video output file");
453be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        }
454be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        releaseMediaRecorder();
455be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        mMediaRecorder = new MediaRecorder();
456be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        try {
45704f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih            if (mOutputFormat == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) {
45804f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
45904f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
46004f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
46104f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setOutputFile(outputFileName);
46204f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoEncodingBitRate(mEncBitRate);
46304f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoFrameRate(FRAME_RATE);
46404f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoSize(mStreamSize.getWidth(), mStreamSize.getHeight());
46504f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
46604f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
46704f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setOrientationHint(mOrientation);
46804f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih            } else {
46904f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                // TODO audio support
47004f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
47104f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.WEBM);
47204f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setOutputFile(outputFileName);
47304f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoEncodingBitRate(mEncBitRate);
47404f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoFrameRate(FRAME_RATE);
47504f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoSize(mStreamSize.getWidth(), mStreamSize.getHeight());
47604f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih                mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.VP8);
47704f1cbebf1d5d8ff9c25dcfa08b66c87abefe9b4Robert Shih            }
478be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mMediaRecorder.prepare();
479be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            mRecordingSurface = mMediaRecorder.getSurface();
480be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        } catch (IllegalStateException e) {
481be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            Log.v(TAG, "MediaRecorder throws IllegalStateException " + e.toString());
482be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        } catch (IOException e) {
483be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang            Log.v(TAG, "MediaRecorder throws IOException " + e.toString());
484be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang        }
485be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang    }
486be9ee01b4c6f1660b9f0cbfdcb2ea57335e5394cChong Zhang
4879d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    /**
4889d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * Do encoding by using MediaCodec encoder, then extracts all pending data
4899d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * from the encoder and forwards it to the muxer.
4909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * <p>
4919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * If notifyEndOfStream is not set, this returns when there is no more data
4929d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * to output. If it is set, we send EOS to the encoder, and then iterate
4939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * until we see EOS on the output. Calling this with notifyEndOfStream set
4949d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * should be done once, before stopping the muxer.
4959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * </p>
4969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * <p>
4979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * We're just using the muxer to get a .mp4 file and audio is not included
4989d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * here.
4999d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     * </p>
5009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He     */
5019d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    private void doMediaCodecEncoding(boolean notifyEndOfStream) {
5029d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (VERBOSE) {
5039d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            Log.v(TAG, "doMediaCodecEncoding(" + notifyEndOfStream + ")");
5049d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
5059d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5069d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        if (notifyEndOfStream) {
5079d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            mEncoder.signalEndOfInputStream();
5089d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        }
5099d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5109d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
5119d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        boolean notDone = true;
5129d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        while (notDone) {
5139d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
5149d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
5159d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (!notifyEndOfStream) {
5169d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    /**
5179d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * Break out of the while loop because the encoder is not
5189d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * ready to output anything yet.
5199d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     */
5209d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    notDone = false;
5219d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                } else {
5229d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    if (VERBOSE) {
5239d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        Log.v(TAG, "no output available, spinning to await EOS");
5249d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    }
5259d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5269d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
5279d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                // generic case for mediacodec, not likely occurs for encoder.
5289d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                encoderOutputBuffers = mEncoder.getOutputBuffers();
5299d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
5309d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                /**
5319d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 * should happen before receiving buffers, and should only
5329d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 * happen once
5339d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                 */
5349d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (mMuxerStarted) {
5359d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    throw new IllegalStateException("format changed twice");
5369d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5379d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                MediaFormat newFormat = mEncoder.getOutputFormat();
5389d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (VERBOSE) {
5399d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    Log.v(TAG, "encoder output format changed: " + newFormat);
5409d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5419d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mTrackIndex = mMuxer.addTrack(newFormat);
5429d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mMuxer.start();
5439d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mMuxerStarted = true;
5449d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else if (encoderStatus < 0) {
5459d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
5469d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            } else {
5479d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                // Normal flow: get output encoded buffer, send to muxer.
5489d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
5499d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (encodedData == null) {
5509d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    throw new RuntimeException("encoderOutputBuffer " + encoderStatus +
5519d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                            " was null");
5529d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5539d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5549d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
5559d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    /**
5569d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * The codec config data was pulled out and fed to the muxer
5579d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * when we got the INFO_OUTPUT_FORMAT_CHANGED status. Ignore
5589d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * it.
5599d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     */
5609d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    if (VERBOSE) {
5619d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        Log.v(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
5629d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    }
5639d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    mBufferInfo.size = 0;
5649d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5659d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5669d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if (mBufferInfo.size != 0) {
5679d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    if (!mMuxerStarted) {
5689d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        throw new RuntimeException("muxer hasn't started");
5699d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    }
5709d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5719d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    /**
5729d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * It's usually necessary to adjust the ByteBuffer values to
5739d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     * match BufferInfo.
5749d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                     */
5759d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    encodedData.position(mBufferInfo.offset);
5769d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
5779d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5789d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
5799d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    if (VERBOSE) {
5809d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        Log.v(TAG, "sent " + mBufferInfo.size + " bytes to muxer");
5819d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    }
5829d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5839d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5849d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                mEncoder.releaseOutputBuffer(encoderStatus, false);
5859d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He
5869d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
5879d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    if (!notifyEndOfStream) {
5889d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        Log.w(TAG, "reached end of stream unexpectedly");
5899d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    } else {
5909d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        if (VERBOSE) {
5919d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                            Log.v(TAG, "end of stream reached");
5929d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                        }
5939d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    }
5949d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    // Finish encoding.
5959d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                    notDone = false;
5969d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He                }
5979d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He            }
5989d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He        } // End of while(notDone)
5999d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He    }
6009d42c135c172cab66b7eca856cd578ed85d512f6Zhijun He}
601