MediaMuxer.java revision 535daa191be782e147d0a115846135fe4229cf0b
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("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4); 39 * // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat() 40 * // or MediaExtractor.getTrackFormat(). 41 * MediaFormat audioFormat = new MediaFormat(...); 42 * MediaFormat videoFormat = new MediaFormat(...); 43 * int audioTrackIndex = muxer.addTrack(audioFormat); 44 * int videoTrackIndex = muxer.addTrack(videoFormat); 45 * ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize); 46 * boolean finished = false; 47 * BufferInfo bufferInfo = new BufferInfo(); 48 * 49 * muxer.start(); 50 * while(!finished) { 51 * // getInputBuffer() will fill the inputBuffer with one frame of encoded 52 * // sample from either MediaCodec or MediaExtractor, set isAudioSample to 53 * // true when the sample is audio data, set up all the fields of bufferInfo, 54 * // and return true if there are no more samples. 55 * finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo); 56 * if (!finished) { 57 * int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; 58 * muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); 59 * } 60 * }; 61 * muxer.stop(); 62 * muxer.release(); 63 * </pre> 64 */ 65 66final public class MediaMuxer { 67 68 static { 69 System.loadLibrary("media_jni"); 70 } 71 72 /** 73 * Defines the output format. These constants are used with constructor. 74 */ 75 public static final class OutputFormat { 76 /* Do not change these values without updating their counterparts 77 * in include/media/stagefright/MediaMuxer.h! 78 */ 79 private OutputFormat() {} 80 /** MPEG4 media file format*/ 81 public static final int MUXER_OUTPUT_MPEG_4 = 0; 82 }; 83 84 // All the native functions are listed here. 85 private static native long nativeSetup(FileDescriptor fd, int format); 86 private static native void nativeRelease(long nativeObject); 87 private static native void nativeStart(long nativeObject); 88 private static native void nativeStop(long nativeObject); 89 private static native int nativeAddTrack(long nativeObject, String[] keys, 90 Object[] values); 91 private static native void nativeSetOrientationHint(long nativeObject, 92 int degrees); 93 private static native void nativeSetLocation(long nativeObject, int latitude, int longitude); 94 private static native void nativeWriteSampleData(long nativeObject, 95 int trackIndex, ByteBuffer byteBuf, 96 int offset, int size, long presentationTimeUs, int flags); 97 98 // Muxer internal states. 99 private static final int MUXER_STATE_UNINITIALIZED = -1; 100 private static final int MUXER_STATE_INITIALIZED = 0; 101 private static final int MUXER_STATE_STARTED = 1; 102 private static final int MUXER_STATE_STOPPED = 2; 103 104 private int mState = MUXER_STATE_UNINITIALIZED; 105 106 private final CloseGuard mCloseGuard = CloseGuard.get(); 107 private int mLastTrackIndex = -1; 108 109 private long mNativeObject; 110 111 /** 112 * Constructor. 113 * Creates a media muxer that writes to the specified path. 114 * @param path The path of the output media file. 115 * @param format The format of the output media file. 116 * @see android.media.MediaMuxer.OutputFormat 117 * @throws IOException if failed to open the file for write 118 */ 119 public MediaMuxer(String path, int format) throws IOException { 120 if (path == null) { 121 throw new IllegalArgumentException("path must not be null"); 122 } 123 if (format != OutputFormat.MUXER_OUTPUT_MPEG_4) { 124 throw new IllegalArgumentException("format is invalid"); 125 } 126 FileOutputStream fos = null; 127 try { 128 File file = new File(path); 129 fos = new FileOutputStream(file); 130 FileDescriptor fd = fos.getFD(); 131 mNativeObject = nativeSetup(fd, format); 132 mState = MUXER_STATE_INITIALIZED; 133 mCloseGuard.open("release"); 134 } finally { 135 if (fos != null) { 136 fos.close(); 137 } 138 } 139 } 140 141 /** 142 * Sets the orientation hint for output video playback. 143 * <p>This method should be called before {@link #start}. Calling this 144 * method will not rotate the video frame when muxer is generating the file, 145 * but add a composition matrix containing the rotation angle in the output 146 * video if the output format is 147 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can 148 * choose the proper orientation for playback. Note that some video players 149 * may choose to ignore the composition matrix in a video during playback. 150 * By default, the rotation degree is 0.</p> 151 * @param degrees the angle to be rotated clockwise in degrees. 152 * The supported angles are 0, 90, 180, and 270 degrees. 153 */ 154 public void setOrientationHint(int degrees) { 155 if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { 156 throw new IllegalArgumentException("Unsupported angle: " + degrees); 157 } 158 if (mState == MUXER_STATE_INITIALIZED) { 159 nativeSetOrientationHint(mNativeObject, degrees); 160 } else { 161 throw new IllegalStateException("Can't set rotation degrees due" + 162 " to wrong state."); 163 } 164 } 165 166 /** 167 * Set and store the geodata (latitude and longitude) in the output file. 168 * This method should be called before {@link #start}. The geodata is stored 169 * in udta box if the output format is 170 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output 171 * formats. The geodata is stored according to ISO-6709 standard. 172 * 173 * @param latitude Latitude in degrees. Its value must be in the range [-90, 174 * 90]. 175 * @param longitude Longitude in degrees. Its value must be in the range 176 * [-180, 180]. 177 * @throws IllegalArgumentException If the given latitude or longitude is out 178 * of range. 179 * @throws IllegalStateException If this method is called after {@link #start}. 180 */ 181 public void setLocation(float latitude, float longitude) { 182 int latitudex10000 = (int) (latitude * 10000 + 0.5); 183 int longitudex10000 = (int) (longitude * 10000 + 0.5); 184 185 if (latitudex10000 > 900000 || latitudex10000 < -900000) { 186 String msg = "Latitude: " + latitude + " out of range."; 187 throw new IllegalArgumentException(msg); 188 } 189 if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { 190 String msg = "Longitude: " + longitude + " out of range"; 191 throw new IllegalArgumentException(msg); 192 } 193 194 if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { 195 nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); 196 } else { 197 throw new IllegalStateException("Can't set location due to wrong state."); 198 } 199 } 200 201 /** 202 * Starts the muxer. 203 * <p>Make sure this is called after {@link #addTrack} and before 204 * {@link #writeSampleData}.</p> 205 */ 206 public void start() { 207 if (mNativeObject == 0) { 208 throw new IllegalStateException("Muxer has been released!"); 209 } 210 if (mState == MUXER_STATE_INITIALIZED) { 211 nativeStart(mNativeObject); 212 mState = MUXER_STATE_STARTED; 213 } else { 214 throw new IllegalStateException("Can't start due to wrong state."); 215 } 216 } 217 218 /** 219 * Stops the muxer. 220 * <p>Once the muxer stops, it can not be restarted.</p> 221 */ 222 public void stop() { 223 if (mState == MUXER_STATE_STARTED) { 224 nativeStop(mNativeObject); 225 mState = MUXER_STATE_STOPPED; 226 } else { 227 throw new IllegalStateException("Can't stop due to wrong state."); 228 } 229 } 230 231 @Override 232 protected void finalize() throws Throwable { 233 try { 234 if (mCloseGuard != null) { 235 mCloseGuard.warnIfOpen(); 236 } 237 if (mNativeObject != 0) { 238 nativeRelease(mNativeObject); 239 mNativeObject = 0; 240 } 241 } finally { 242 super.finalize(); 243 } 244 } 245 246 /** 247 * Adds a track with the specified format. 248 * @param format The media format for the track. 249 * @return The track index for this newly added track, and it should be used 250 * in the {@link #writeSampleData}. 251 */ 252 public int addTrack(MediaFormat format) { 253 if (format == null) { 254 throw new IllegalArgumentException("format must not be null."); 255 } 256 if (mState != MUXER_STATE_INITIALIZED) { 257 throw new IllegalStateException("Muxer is not initialized."); 258 } 259 if (mNativeObject == 0) { 260 throw new IllegalStateException("Muxer has been released!"); 261 } 262 int trackIndex = -1; 263 // Convert the MediaFormat into key-value pairs and send to the native. 264 Map<String, Object> formatMap = format.getMap(); 265 266 String[] keys = null; 267 Object[] values = null; 268 int mapSize = formatMap.size(); 269 if (mapSize > 0) { 270 keys = new String[mapSize]; 271 values = new Object[mapSize]; 272 int i = 0; 273 for (Map.Entry<String, Object> entry : formatMap.entrySet()) { 274 keys[i] = entry.getKey(); 275 values[i] = entry.getValue(); 276 ++i; 277 } 278 trackIndex = nativeAddTrack(mNativeObject, keys, values); 279 } else { 280 throw new IllegalArgumentException("format must not be empty."); 281 } 282 283 // Track index number is expected to incremented as addTrack succeed. 284 // However, if format is invalid, it will get a negative trackIndex. 285 if (mLastTrackIndex >= trackIndex) { 286 throw new IllegalArgumentException("Invalid format."); 287 } 288 mLastTrackIndex = trackIndex; 289 return trackIndex; 290 } 291 292 /** 293 * Writes an encoded sample into the muxer. 294 * <p>The application needs to make sure that the samples are written into 295 * the right tracks. Also, it needs to make sure the samples for each track 296 * are written in chronological order (e.g. in the order they are provided 297 * by the encoder.)</p> 298 * @param byteBuf The encoded sample. 299 * @param trackIndex The track index for this sample. 300 * @param bufferInfo The buffer information related to this sample. 301 * MediaMuxer uses the flags provided in {@link MediaCodec.BufferInfo}, 302 * to signal sync frames. 303 */ 304 public void writeSampleData(int trackIndex, ByteBuffer byteBuf, 305 BufferInfo bufferInfo) { 306 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 307 throw new IllegalArgumentException("trackIndex is invalid"); 308 } 309 310 if (byteBuf == null) { 311 throw new IllegalArgumentException("byteBuffer must not be null"); 312 } 313 314 if (bufferInfo == null) { 315 throw new IllegalArgumentException("bufferInfo must not be null"); 316 } 317 if (bufferInfo.size < 0 || bufferInfo.offset < 0 318 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 319 || bufferInfo.presentationTimeUs < 0) { 320 throw new IllegalArgumentException("bufferInfo must specify a" + 321 " valid buffer offset, size and presentation time"); 322 } 323 324 if (mNativeObject == 0) { 325 throw new IllegalStateException("Muxer has been released!"); 326 } 327 328 if (mState != MUXER_STATE_STARTED) { 329 throw new IllegalStateException("Can't write, muxer is not started"); 330 } 331 332 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 333 bufferInfo.offset, bufferInfo.size, 334 bufferInfo.presentationTimeUs, bufferInfo.flags); 335 } 336 337 /** 338 * Make sure you call this when you're done to free up any resources 339 * instead of relying on the garbage collector to do this for you at 340 * some point in the future. 341 */ 342 public void release() { 343 if (mState == MUXER_STATE_STARTED) { 344 stop(); 345 } 346 if (mNativeObject != 0) { 347 nativeRelease(mNativeObject); 348 mNativeObject = 0; 349 mCloseGuard.close(); 350 } 351 mState = MUXER_STATE_UNINITIALIZED; 352 } 353} 354