MediaMuxer.java revision ecca7f60a69d99a9569c06bdf9c122f853e67d47
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 // All the native functions are listed here. 78 private static native int nativeSetup(FileDescriptor fd, int format); 79 private static native void nativeRelease(int nativeObject); 80 private static native void nativeStart(int nativeObject); 81 private static native void nativeStop(int nativeObject); 82 private static native int nativeAddTrack(int nativeObject, String[] keys, 83 Object[] values); 84 private static native void nativeSetOrientationHint(int nativeObject, 85 int degrees); 86 private static native void nativeWriteSampleData(int nativeObject, 87 int trackIndex, ByteBuffer byteBuf, 88 int offset, int size, long presentationTimeUs, int flags); 89 90 // Muxer internal states. 91 private static final int MUXER_STATE_UNINITIALIZED = -1; 92 private static final int MUXER_STATE_INITIALIZED = 0; 93 private static final int MUXER_STATE_STARTED = 1; 94 private static final int MUXER_STATE_STOPPED = 2; 95 96 private int mState = MUXER_STATE_UNINITIALIZED; 97 98 private final CloseGuard mCloseGuard = CloseGuard.get(); 99 private int mLastTrackIndex = -1; 100 101 private int mNativeObject; 102 103 /** 104 * Constructor. 105 * Creates a media muxer that writes to the specified path. 106 * @param path The path of the output media file. 107 * @param format The format of the output media file. 108 * @see android.media.MediaMuxer.OutputFormat 109 * @throws IOException if failed to open the file for write 110 */ 111 public MediaMuxer(String path, int format) throws IOException { 112 if (path == null) { 113 throw new IllegalArgumentException("path must not be null"); 114 } 115 if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) { 116 throw new IllegalArgumentException("format is invalid"); 117 } 118 FileOutputStream fos = null; 119 try { 120 File file = new File(path); 121 fos = new FileOutputStream(file); 122 FileDescriptor fd = fos.getFD(); 123 mNativeObject = nativeSetup(fd, format); 124 mState = MUXER_STATE_INITIALIZED; 125 mCloseGuard.open("release"); 126 } finally { 127 if (fos != null) { 128 fos.close(); 129 } 130 } 131 } 132 133 /** 134 * Sets the orientation hint for output video playback. 135 * <p>This method should be called before {@link #start}. Calling this 136 * method will not rotate the video frame when muxer is generating the file, 137 * but add a composition matrix containing the rotation angle in the output 138 * video if the output format is 139 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can 140 * choose the proper orientation for playback. Note that some video players 141 * may choose to ignore the composition matrix in a video during playback. 142 * By default, the rotation degree is 0.</p> 143 * @param degrees the angle to be rotated clockwise in degrees. 144 * The supported angles are 0, 90, 180, and 270 degrees. 145 */ 146 public void setOrientationHint(int degrees) { 147 if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { 148 throw new IllegalArgumentException("Unsupported angle: " + degrees); 149 } 150 if (mState == MUXER_STATE_INITIALIZED) { 151 nativeSetOrientationHint(mNativeObject, degrees); 152 } else { 153 throw new IllegalStateException("Can't set rotation degrees due" + 154 " to wrong state."); 155 } 156 } 157 158 /** 159 * Starts the muxer. 160 * <p>Make sure this is called after {@link #addTrack} and before 161 * {@link #writeSampleData}.</p> 162 */ 163 public void start() { 164 if (mNativeObject == 0) { 165 throw new IllegalStateException("Muxer has been released!"); 166 } 167 if (mState == MUXER_STATE_INITIALIZED) { 168 nativeStart(mNativeObject); 169 mState = MUXER_STATE_STARTED; 170 } else { 171 throw new IllegalStateException("Can't start due to wrong state."); 172 } 173 } 174 175 /** 176 * Stops the muxer. 177 * <p>Once the muxer stops, it can not be restarted.</p> 178 */ 179 public void stop() { 180 if (mState == MUXER_STATE_STARTED) { 181 nativeStop(mNativeObject); 182 mState = MUXER_STATE_STOPPED; 183 } else { 184 throw new IllegalStateException("Can't stop due to wrong state."); 185 } 186 } 187 188 @Override 189 protected void finalize() throws Throwable { 190 try { 191 if (mCloseGuard != null) { 192 mCloseGuard.warnIfOpen(); 193 } 194 if (mNativeObject != 0) { 195 nativeRelease(mNativeObject); 196 mNativeObject = 0; 197 } 198 } finally { 199 super.finalize(); 200 } 201 } 202 203 /** 204 * Adds a track with the specified format. 205 * @param format The media format for the track. 206 * @return The track index for this newly added track, and it should be used 207 * in the {@link #writeSampleData}. 208 */ 209 public int addTrack(MediaFormat format) { 210 if (format == null) { 211 throw new IllegalArgumentException("format must not be null."); 212 } 213 if (mState != MUXER_STATE_INITIALIZED) { 214 throw new IllegalStateException("Muxer is not initialized."); 215 } 216 if (mNativeObject == 0) { 217 throw new IllegalStateException("Muxer has been released!"); 218 } 219 int trackIndex = -1; 220 // Convert the MediaFormat into key-value pairs and send to the native. 221 Map<String, Object> formatMap = format.getMap(); 222 223 String[] keys = null; 224 Object[] values = null; 225 int mapSize = formatMap.size(); 226 if (mapSize > 0) { 227 keys = new String[mapSize]; 228 values = new Object[mapSize]; 229 int i = 0; 230 for (Map.Entry<String, Object> entry : formatMap.entrySet()) { 231 keys[i] = entry.getKey(); 232 values[i] = entry.getValue(); 233 ++i; 234 } 235 trackIndex = nativeAddTrack(mNativeObject, keys, values); 236 } else { 237 throw new IllegalArgumentException("format must not be empty."); 238 } 239 240 // Track index number is expected to incremented as addTrack succeed. 241 // However, if format is invalid, it will get a negative trackIndex. 242 if (mLastTrackIndex >= trackIndex) { 243 throw new IllegalArgumentException("Invalid format."); 244 } 245 mLastTrackIndex = trackIndex; 246 return trackIndex; 247 } 248 249 /** 250 * Writes an encoded sample into the muxer. 251 * <p>The application needs to make sure that the samples are written into 252 * the right tracks. Also, it needs to make sure the samples for each track 253 * are written in chronological order (e.g. in the order they are provided 254 * by the encoder.)</p> 255 * @param byteBuf The encoded sample. 256 * @param trackIndex The track index for this sample. 257 * @param bufferInfo The buffer information related to this sample. 258 * MediaMuxer uses the flags provided in {@link MediaCodec.BufferInfo}, 259 * to signal sync frames. 260 */ 261 public void writeSampleData(int trackIndex, ByteBuffer byteBuf, 262 BufferInfo bufferInfo) { 263 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 264 throw new IllegalArgumentException("trackIndex is invalid"); 265 } 266 267 if (byteBuf == null) { 268 throw new IllegalArgumentException("byteBuffer must not be null"); 269 } 270 271 if (bufferInfo == null) { 272 throw new IllegalArgumentException("bufferInfo must not be null"); 273 } 274 if (bufferInfo.size < 0 || bufferInfo.offset < 0 275 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 276 || bufferInfo.presentationTimeUs < 0) { 277 throw new IllegalArgumentException("bufferInfo must specify a" + 278 " valid buffer offset, size and presentation time"); 279 } 280 281 if (mNativeObject == 0) { 282 throw new IllegalStateException("Muxer has been released!"); 283 } 284 285 if (mState != MUXER_STATE_STARTED) { 286 throw new IllegalStateException("Can't write, muxer is not started"); 287 } 288 289 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 290 bufferInfo.offset, bufferInfo.size, 291 bufferInfo.presentationTimeUs, bufferInfo.flags); 292 } 293 294 /** 295 * Make sure you call this when you're done to free up any resources 296 * instead of relying on the garbage collector to do this for you at 297 * some point in the future. 298 */ 299 public void release() { 300 if (mState == MUXER_STATE_STARTED) { 301 stop(); 302 } 303 if (mNativeObject != 0) { 304 nativeRelease(mNativeObject); 305 mNativeObject = 0; 306 mCloseGuard.close(); 307 } 308 mState = MUXER_STATE_UNINITIALIZED; 309 } 310} 311