MediaMuxer.java revision effc9b4839f3cc109fe3d8244022f3c898cd080b
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media; 18 19import android.media.MediaCodec.BufferInfo; 20 21import dalvik.system.CloseGuard; 22 23import java.io.File; 24import java.io.FileDescriptor; 25import java.io.FileOutputStream; 26import java.io.IOException; 27import java.nio.ByteBuffer; 28import java.util.Map; 29 30/** 31 * MediaMuxer facilitates muxing elementary streams. Currently only supports an 32 * mp4 file as the output and at most one audio and/or one video elementary 33 * stream. 34 * <p> 35 * It is generally used like this: 36 * 37 * <pre> 38 * MediaMuxer muxer = new MediaMuxer(...); 39 * MediaFormat audioFormat = new MediaFormat(...); 40 * MediaFormat videoFormat = new MediaFormat(...); 41 * int audioTrackIndex = muxer.addTrack(audioFormat); 42 * int videoTrackIndex = muxer.addTrack(videoFormat); 43 * ByteBuffer inputBuffer = ByteBuffer.allocate(...); 44 * muxer.start(); 45 * while(inputBuffer has new data) { 46 * if (new data is audio sample) { 47 * muxer.writeSampleData(audioTrackIndex, inputBuffer, ...); 48 * } else if (new data is video sample) { 49 * muxer.writeSampleData(videoTrackIndex, inputBuffer, ...); 50 * } 51 * } 52 * muxer.stop(); 53 * muxer.release(); 54 * </pre> 55 */ 56 57final public class MediaMuxer { 58 59 private int mNativeContext; 60 61 static { 62 System.loadLibrary("media_jni"); 63 } 64 65 /** 66 * Defines the output format. These constants are used with constructor. 67 */ 68 public static final class OutputFormat { 69 /* Do not change these values without updating their counterparts 70 * in include/media/stagefright/MediaMuxer.h! 71 */ 72 private OutputFormat() {} 73 /** MPEG4 media file format*/ 74 public static final int MUXER_OUTPUT_MPEG_4 = 0; 75 }; 76 77 /** 78 * The sample is a sync sample, which does not require other video samples 79 * to decode. This flag is used in {@link #writeSampleData} to indicate 80 * which sample is a sync sample. 81 */ 82 /* Keep this flag in sync with its equivalent in 83 * include/media/stagefright/MediaMuxer.h. 84 */ 85 public static final int SAMPLE_FLAG_SYNC = 1; 86 87 // All the native functions are listed here. 88 private static native int nativeSetup(FileDescriptor fd, int format); 89 private static native void nativeRelease(int nativeObject); 90 private static native void nativeStart(int nativeObject); 91 private static native void nativeStop(int nativeObject); 92 private static native int nativeAddTrack(int nativeObject, String[] keys, 93 Object[] values); 94 private static native void nativeSetOrientationHint(int nativeObject, 95 int degrees); 96 private static native void nativeWriteSampleData(int nativeObject, 97 int trackIndex, ByteBuffer byteBuf, 98 int offset, int size, long presentationTimeUs, int flags); 99 100 // Muxer internal states. 101 private static final int MUXER_STATE_UNINITIALIZED = -1; 102 private static final int MUXER_STATE_INITIALIZED = 0; 103 private static final int MUXER_STATE_STARTED = 1; 104 private static final int MUXER_STATE_STOPPED = 2; 105 106 private int mState = MUXER_STATE_UNINITIALIZED; 107 108 private final CloseGuard mCloseGuard = CloseGuard.get(); 109 private int mLastTrackIndex = -1; 110 111 private int mNativeObject; 112 113 /** 114 * Constructor. 115 * Creates a media muxer that writes to the specified path. 116 * @param path The path of the output media file. 117 * @param format The format of the output media file. 118 * @see android.media.MediaMuxer.OutputFormat 119 * @throws IOException if failed to open the file for write 120 */ 121 public MediaMuxer(String path, int format) throws IOException { 122 if (path == null) { 123 throw new IllegalArgumentException("path must not be null"); 124 } 125 if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) { 126 throw new IllegalArgumentException("format is invalid"); 127 } 128 FileOutputStream fos = null; 129 try { 130 File file = new File(path); 131 fos = new FileOutputStream(file); 132 FileDescriptor fd = fos.getFD(); 133 mNativeObject = nativeSetup(fd, format); 134 mState = MUXER_STATE_INITIALIZED; 135 mCloseGuard.open("release"); 136 } finally { 137 if (fos != null) { 138 fos.close(); 139 } 140 } 141 } 142 143 /** 144 * Sets the orientation hint for output video playback. 145 * <p>This method should be called before {@link #start}. Calling this 146 * method will not rotate the video frame when muxer is generating the file, 147 * but add a composition matrix containing the rotation angle in the output 148 * video if the output format is 149 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can 150 * choose the proper orientation for playback. Note that some video players 151 * may choose to ignore the composition matrix in a video during playback. 152 * By default, the rotation degree is 0.</p> 153 * @param degrees the angle to be rotated clockwise in degrees. 154 * The supported angles are 0, 90, 180, and 270 degrees. 155 */ 156 public void setOrientationHint(int degrees) { 157 if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { 158 throw new IllegalArgumentException("Unsupported angle: " + degrees); 159 } 160 if (mState == MUXER_STATE_INITIALIZED) { 161 nativeSetOrientationHint(mNativeObject, degrees); 162 } else { 163 throw new IllegalStateException("Can't set rotation degrees due" + 164 " to wrong state."); 165 } 166 } 167 168 /** 169 * Starts the muxer. 170 * <p>Make sure this is called after {@link #addTrack} and before 171 * {@link #writeSampleData}.</p> 172 */ 173 public void start() { 174 if (mNativeObject == 0) { 175 throw new IllegalStateException("Muxer has been released!"); 176 } 177 if (mState == MUXER_STATE_INITIALIZED) { 178 nativeStart(mNativeObject); 179 mState = MUXER_STATE_STARTED; 180 } else { 181 throw new IllegalStateException("Can't start due to wrong state."); 182 } 183 } 184 185 /** 186 * Stops the muxer. 187 * <p>Once the muxer stops, it can not be restarted.</p> 188 */ 189 public void stop() { 190 if (mState == MUXER_STATE_STARTED) { 191 nativeStop(mNativeObject); 192 mState = MUXER_STATE_STOPPED; 193 } else { 194 throw new IllegalStateException("Can't stop due to wrong state."); 195 } 196 } 197 198 @Override 199 protected void finalize() throws Throwable { 200 try { 201 if (mCloseGuard != null) { 202 mCloseGuard.warnIfOpen(); 203 } 204 if (mNativeObject != 0) { 205 nativeRelease(mNativeObject); 206 mNativeObject = 0; 207 } 208 } finally { 209 super.finalize(); 210 } 211 } 212 213 /** 214 * Adds a track with the specified format. 215 * @param format The media format for the track. 216 * @return The track index for this newly added track, and it should be used 217 * in the {@link #writeSampleData}. 218 */ 219 public int addTrack(MediaFormat format) { 220 if (format == null) { 221 throw new IllegalArgumentException("format must not be null."); 222 } 223 if (mState != MUXER_STATE_INITIALIZED) { 224 throw new IllegalStateException("Muxer is not initialized."); 225 } 226 if (mNativeObject == 0) { 227 throw new IllegalStateException("Muxer has been released!"); 228 } 229 int trackIndex = -1; 230 // Convert the MediaFormat into key-value pairs and send to the native. 231 Map<String, Object> formatMap = format.getMap(); 232 233 String[] keys = null; 234 Object[] values = null; 235 int mapSize = formatMap.size(); 236 if (mapSize > 0) { 237 keys = new String[mapSize]; 238 values = new Object[mapSize]; 239 int i = 0; 240 for (Map.Entry<String, Object> entry : formatMap.entrySet()) { 241 keys[i] = entry.getKey(); 242 values[i] = entry.getValue(); 243 ++i; 244 } 245 trackIndex = nativeAddTrack(mNativeObject, keys, values); 246 } else { 247 throw new IllegalArgumentException("format must not be empty."); 248 } 249 250 // Track index number is expected to incremented as addTrack succeed. 251 // However, if format is invalid, it will get a negative trackIndex. 252 if (mLastTrackIndex >= trackIndex) { 253 throw new IllegalArgumentException("Invalid format."); 254 } 255 mLastTrackIndex = trackIndex; 256 return trackIndex; 257 } 258 259 /** 260 * Writes an encoded sample into the muxer. The application needs to make 261 * sure that the samples are written into the right tracks. 262 * @param byteBuf The encoded sample. 263 * @param trackIndex The track index for this sample. 264 * @param bufferInfo The buffer information related to this sample. 265 */ 266 public void writeSampleData(int trackIndex, ByteBuffer byteBuf, 267 BufferInfo bufferInfo) { 268 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 269 throw new IllegalArgumentException("trackIndex is invalid"); 270 } 271 272 if (byteBuf == null) { 273 throw new IllegalArgumentException("byteBuffer must not be null"); 274 } 275 276 if (bufferInfo == null) { 277 throw new IllegalArgumentException("bufferInfo must not be null"); 278 } 279 if (bufferInfo.size < 0 || bufferInfo.offset < 0 280 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 281 || bufferInfo.presentationTimeUs < 0) { 282 throw new IllegalArgumentException("bufferInfo must specify a" + 283 " valid buffer offset, size and presentation time"); 284 } 285 286 if (mNativeObject == 0) { 287 throw new IllegalStateException("Muxer has been released!"); 288 } 289 290 if (mState != MUXER_STATE_STARTED) { 291 throw new IllegalStateException("Can't write, muxer is not started"); 292 } 293 294 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 295 bufferInfo.offset, bufferInfo.size, 296 bufferInfo.presentationTimeUs, bufferInfo.flags); 297 } 298 299 /** 300 * Make sure you call this when you're done to free up any resources 301 * instead of relying on the garbage collector to do this for you at 302 * some point in the future. 303 */ 304 public void release() { 305 if (mState == MUXER_STATE_STARTED) { 306 stop(); 307 } 308 if (mNativeObject != 0) { 309 nativeRelease(mNativeObject); 310 mNativeObject = 0; 311 mCloseGuard.close(); 312 } 313 mState = MUXER_STATE_UNINITIALIZED; 314 } 315} 316