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