MediaMuxer.java revision f7d3aae32859a52c24713dba30e4d7ef779fdfb1
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. 261 * <p>The application needs to make sure that the samples are written into 262 * the right tracks. Also, it needs to make sure the samples for each track 263 * are written in chronological order.</p> 264 * @param byteBuf The encoded sample. 265 * @param trackIndex The track index for this sample. 266 * @param bufferInfo The buffer information related to this sample. 267 */ 268 public void writeSampleData(int trackIndex, ByteBuffer byteBuf, 269 BufferInfo bufferInfo) { 270 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 271 throw new IllegalArgumentException("trackIndex is invalid"); 272 } 273 274 if (byteBuf == null) { 275 throw new IllegalArgumentException("byteBuffer must not be null"); 276 } 277 278 if (bufferInfo == null) { 279 throw new IllegalArgumentException("bufferInfo must not be null"); 280 } 281 if (bufferInfo.size < 0 || bufferInfo.offset < 0 282 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 283 || bufferInfo.presentationTimeUs < 0) { 284 throw new IllegalArgumentException("bufferInfo must specify a" + 285 " valid buffer offset, size and presentation time"); 286 } 287 288 if (mNativeObject == 0) { 289 throw new IllegalStateException("Muxer has been released!"); 290 } 291 292 if (mState != MUXER_STATE_STARTED) { 293 throw new IllegalStateException("Can't write, muxer is not started"); 294 } 295 296 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 297 bufferInfo.offset, bufferInfo.size, 298 bufferInfo.presentationTimeUs, bufferInfo.flags); 299 } 300 301 /** 302 * Make sure you call this when you're done to free up any resources 303 * instead of relying on the garbage collector to do this for you at 304 * some point in the future. 305 */ 306 public void release() { 307 if (mState == MUXER_STATE_STARTED) { 308 stop(); 309 } 310 if (mNativeObject != 0) { 311 nativeRelease(mNativeObject); 312 mNativeObject = 0; 313 mCloseGuard.close(); 314 } 315 mState = MUXER_STATE_UNINITIALIZED; 316 } 317} 318