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.annotation.IntDef; 20import android.annotation.NonNull; 21import android.annotation.Nullable; 22import android.media.MediaCodec; 23import android.media.MediaCodec.BufferInfo; 24import dalvik.system.CloseGuard; 25 26import java.io.FileDescriptor; 27import java.io.IOException; 28import java.io.RandomAccessFile; 29import java.lang.annotation.Retention; 30import java.lang.annotation.RetentionPolicy; 31import java.nio.ByteBuffer; 32import java.util.Map; 33 34/** 35 * MediaMuxer facilitates muxing elementary streams. Currently supports mp4 or 36 * webm file as the output and at most one audio and/or one video elementary 37 * stream. MediaMuxer does not support muxing B-frames. 38 * <p> 39 * It is generally used like this: 40 * 41 * <pre> 42 * MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4); 43 * // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat() 44 * // or MediaExtractor.getTrackFormat(). 45 * MediaFormat audioFormat = new MediaFormat(...); 46 * MediaFormat videoFormat = new MediaFormat(...); 47 * int audioTrackIndex = muxer.addTrack(audioFormat); 48 * int videoTrackIndex = muxer.addTrack(videoFormat); 49 * ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize); 50 * boolean finished = false; 51 * BufferInfo bufferInfo = new BufferInfo(); 52 * 53 * muxer.start(); 54 * while(!finished) { 55 * // getInputBuffer() will fill the inputBuffer with one frame of encoded 56 * // sample from either MediaCodec or MediaExtractor, set isAudioSample to 57 * // true when the sample is audio data, set up all the fields of bufferInfo, 58 * // and return true if there are no more samples. 59 * finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo); 60 * if (!finished) { 61 * int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; 62 * muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); 63 * } 64 * }; 65 * muxer.stop(); 66 * muxer.release(); 67 * </pre> 68 */ 69 70final public class MediaMuxer { 71 72 static { 73 System.loadLibrary("media_jni"); 74 } 75 76 /** 77 * Defines the output format. These constants are used with constructor. 78 */ 79 public static final class OutputFormat { 80 /* Do not change these values without updating their counterparts 81 * in include/media/stagefright/MediaMuxer.h! 82 */ 83 private OutputFormat() {} 84 /** MPEG4 media file format*/ 85 public static final int MUXER_OUTPUT_MPEG_4 = 0; 86 public static final int MUXER_OUTPUT_WEBM = 1; 87 }; 88 89 /** @hide */ 90 @IntDef({ 91 OutputFormat.MUXER_OUTPUT_MPEG_4, 92 OutputFormat.MUXER_OUTPUT_WEBM, 93 }) 94 @Retention(RetentionPolicy.SOURCE) 95 public @interface Format {} 96 97 // All the native functions are listed here. 98 private static native long nativeSetup(@NonNull FileDescriptor fd, int format); 99 private static native void nativeRelease(long nativeObject); 100 private static native void nativeStart(long nativeObject); 101 private static native void nativeStop(long nativeObject); 102 private static native int nativeAddTrack( 103 long nativeObject, @NonNull String[] keys, @NonNull Object[] values); 104 private static native void nativeSetOrientationHint( 105 long nativeObject, int degrees); 106 private static native void nativeSetLocation(long nativeObject, int latitude, int longitude); 107 private static native void nativeWriteSampleData( 108 long nativeObject, int trackIndex, @NonNull ByteBuffer byteBuf, 109 int offset, int size, long presentationTimeUs, @MediaCodec.BufferFlag int flags); 110 111 // Muxer internal states. 112 private static final int MUXER_STATE_UNINITIALIZED = -1; 113 private static final int MUXER_STATE_INITIALIZED = 0; 114 private static final int MUXER_STATE_STARTED = 1; 115 private static final int MUXER_STATE_STOPPED = 2; 116 117 private int mState = MUXER_STATE_UNINITIALIZED; 118 119 private final CloseGuard mCloseGuard = CloseGuard.get(); 120 private int mLastTrackIndex = -1; 121 122 private long mNativeObject; 123 124 /** 125 * Constructor. 126 * Creates a media muxer that writes to the specified path. 127 * @param path The path of the output media file. 128 * @param format The format of the output media file. 129 * @see android.media.MediaMuxer.OutputFormat 130 * @throws IllegalArgumentException if path is invalid or format is not supported. 131 * @throws IOException if failed to open the file for write. 132 */ 133 public MediaMuxer(@NonNull String path, @Format int format) throws IOException { 134 if (path == null) { 135 throw new IllegalArgumentException("path must not be null"); 136 } 137 if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && 138 format != OutputFormat.MUXER_OUTPUT_WEBM) { 139 throw new IllegalArgumentException("format is invalid"); 140 } 141 // Use RandomAccessFile so we can open the file with RW access; 142 // RW access allows the native writer to memory map the output file. 143 RandomAccessFile file = null; 144 try { 145 file = new RandomAccessFile(path, "rws"); 146 FileDescriptor fd = file.getFD(); 147 mNativeObject = nativeSetup(fd, format); 148 mState = MUXER_STATE_INITIALIZED; 149 mCloseGuard.open("release"); 150 } finally { 151 if (file != null) { 152 file.close(); 153 } 154 } 155 } 156 157 /** 158 * Sets the orientation hint for output video playback. 159 * <p>This method should be called before {@link #start}. Calling this 160 * method will not rotate the video frame when muxer is generating the file, 161 * but add a composition matrix containing the rotation angle in the output 162 * video if the output format is 163 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can 164 * choose the proper orientation for playback. Note that some video players 165 * may choose to ignore the composition matrix in a video during playback. 166 * By default, the rotation degree is 0.</p> 167 * @param degrees the angle to be rotated clockwise in degrees. 168 * The supported angles are 0, 90, 180, and 270 degrees. 169 * @throws IllegalArgumentException if degree is not supported. 170 * @throws IllegalStateException If this method is called after {@link #start}. 171 */ 172 public void setOrientationHint(int degrees) { 173 if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { 174 throw new IllegalArgumentException("Unsupported angle: " + degrees); 175 } 176 if (mState == MUXER_STATE_INITIALIZED) { 177 nativeSetOrientationHint(mNativeObject, degrees); 178 } else { 179 throw new IllegalStateException("Can't set rotation degrees due" + 180 " to wrong state."); 181 } 182 } 183 184 /** 185 * Set and store the geodata (latitude and longitude) in the output file. 186 * This method should be called before {@link #start}. The geodata is stored 187 * in udta box if the output format is 188 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output 189 * formats. The geodata is stored according to ISO-6709 standard. 190 * 191 * @param latitude Latitude in degrees. Its value must be in the range [-90, 192 * 90]. 193 * @param longitude Longitude in degrees. Its value must be in the range 194 * [-180, 180]. 195 * @throws IllegalArgumentException If the given latitude or longitude is out 196 * of range. 197 * @throws IllegalStateException If this method is called after {@link #start}. 198 */ 199 public void setLocation(float latitude, float longitude) { 200 int latitudex10000 = (int) (latitude * 10000 + 0.5); 201 int longitudex10000 = (int) (longitude * 10000 + 0.5); 202 203 if (latitudex10000 > 900000 || latitudex10000 < -900000) { 204 String msg = "Latitude: " + latitude + " out of range."; 205 throw new IllegalArgumentException(msg); 206 } 207 if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { 208 String msg = "Longitude: " + longitude + " out of range"; 209 throw new IllegalArgumentException(msg); 210 } 211 212 if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { 213 nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); 214 } else { 215 throw new IllegalStateException("Can't set location due to wrong state."); 216 } 217 } 218 219 /** 220 * Starts the muxer. 221 * <p>Make sure this is called after {@link #addTrack} and before 222 * {@link #writeSampleData}.</p> 223 * @throws IllegalStateException If this method is called after {@link #start} 224 * or Muxer is released 225 */ 226 public void start() { 227 if (mNativeObject == 0) { 228 throw new IllegalStateException("Muxer has been released!"); 229 } 230 if (mState == MUXER_STATE_INITIALIZED) { 231 nativeStart(mNativeObject); 232 mState = MUXER_STATE_STARTED; 233 } else { 234 throw new IllegalStateException("Can't start due to wrong state."); 235 } 236 } 237 238 /** 239 * Stops the muxer. 240 * <p>Once the muxer stops, it can not be restarted.</p> 241 * @throws IllegalStateException if muxer is in the wrong state. 242 */ 243 public void stop() { 244 if (mState == MUXER_STATE_STARTED) { 245 nativeStop(mNativeObject); 246 mState = MUXER_STATE_STOPPED; 247 } else { 248 throw new IllegalStateException("Can't stop due to wrong state."); 249 } 250 } 251 252 @Override 253 protected void finalize() throws Throwable { 254 try { 255 if (mCloseGuard != null) { 256 mCloseGuard.warnIfOpen(); 257 } 258 if (mNativeObject != 0) { 259 nativeRelease(mNativeObject); 260 mNativeObject = 0; 261 } 262 } finally { 263 super.finalize(); 264 } 265 } 266 267 /** 268 * Adds a track with the specified format. 269 * <p> 270 * The following table summarizes support for specific format keys across android releases. 271 * Keys marked with '+:' are required. 272 * 273 * <table style="width: 0%"> 274 * <thead> 275 * <tr> 276 * <th rowspan=2>OS Version(s)</th> 277 * <td colspan=3>{@code MediaFormat} keys used for</th> 278 * </tr><tr> 279 * <th>All Tracks</th> 280 * <th>Audio Tracks</th> 281 * <th>Video Tracks</th> 282 * </tr> 283 * </thead> 284 * <tbody> 285 * <tr> 286 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> 287 * <td rowspan=7>+: {@link MediaFormat#KEY_MIME}</td> 288 * <td rowspan=3>+: {@link MediaFormat#KEY_SAMPLE_RATE},<br> 289 * +: {@link MediaFormat#KEY_CHANNEL_COUNT},<br> 290 * +: <strong>codec-specific data<sup>AAC</sup></strong></td> 291 * <td rowspan=5>+: {@link MediaFormat#KEY_WIDTH},<br> 292 * +: {@link MediaFormat#KEY_HEIGHT},<br> 293 * no {@code KEY_ROTATION}, 294 * use {@link #setOrientationHint setOrientationHint()}<sup>.mp4</sup>,<br> 295 * +: <strong>codec-specific data<sup>AVC, MPEG4</sup></strong></td> 296 * </tr><tr> 297 * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> 298 * </tr><tr> 299 * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> 300 * </tr><tr> 301 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> 302 * <td rowspan=4>as above, plus<br> 303 * +: <strong>codec-specific data<sup>Vorbis & .webm</sup></strong></td> 304 * </tr><tr> 305 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> 306 * </tr><tr> 307 * <td>{@link android.os.Build.VERSION_CODES#M}</td> 308 * <td>as above, plus<br> 309 * {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup></td> 310 * </tr><tr> 311 * <td>{@link android.os.Build.VERSION_CODES#N}</td> 312 * <td>as above, plus<br> 313 * <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>AAC, MPEG4</sup>,<br> --> 314 * {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br> 315 * {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br> 316 * {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br> 317 * {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br> 318 * {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br> 319 * +: <strong>codec-specific data<sup>HEVC</sup></strong>,<br> 320 * codec-specific data<sup>VP9</sup></td> 321 * </tr> 322 * <tr> 323 * <td colspan=4> 324 * <p class=note><strong>Notes:</strong><br> 325 * #: storing into container metadata.<br> 326 * .mp4, .webm…: for listed containers<br> 327 * MPEG4, AAC…: for listed codecs 328 * </td> 329 * </tr><tr> 330 * <td colspan=4> 331 * <p class=note>Note that the codec-specific data for the track must be specified using 332 * this method. Furthermore, codec-specific data must not be passed/specified via the 333 * {@link #writeSampleData writeSampleData()} call. 334 * </td> 335 * </tr> 336 * </tbody> 337 * </table> 338 * 339 * <p> 340 * The following table summarizes codec support for containers across android releases: 341 * 342 * <table style="width: 0%"> 343 * <thead> 344 * <tr> 345 * <th rowspan=2>OS Version(s)</th> 346 * <td colspan=3>Codec support</th> 347 * </tr><tr> 348 * <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th> 349 * <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th> 350 * </tr> 351 * </thead> 352 * <tbody> 353 * <tr> 354 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> 355 * <td rowspan=6>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC},<br> 356 * {@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR},<br> 357 * {@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR},<br> 358 * {@link MediaFormat#MIMETYPE_VIDEO_H263 H.263},<br> 359 * {@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4},<br> 360 * {@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td> 361 * <td rowspan=3>Not supported</td> 362 * </tr><tr> 363 * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> 364 * </tr><tr> 365 * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> 366 * </tr><tr> 367 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> 368 * <td rowspan=3>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis},<br> 369 * {@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td> 370 * </tr><tr> 371 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> 372 * </tr><tr> 373 * <td>{@link android.os.Build.VERSION_CODES#M}</td> 374 * </tr><tr> 375 * <td>{@link android.os.Build.VERSION_CODES#N}</td> 376 * <td>as above, plus<br> 377 * {@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td> 378 * <td>as above, plus<br> 379 * {@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td> 380 * </tr> 381 * </tbody> 382 * </table> 383 * 384 * @param format The media format for the track. This must not be an empty 385 * MediaFormat. 386 * @return The track index for this newly added track, and it should be used 387 * in the {@link #writeSampleData}. 388 * @throws IllegalArgumentException if format is invalid. 389 * @throws IllegalStateException if muxer is in the wrong state. 390 */ 391 public int addTrack(@NonNull MediaFormat format) { 392 if (format == null) { 393 throw new IllegalArgumentException("format must not be null."); 394 } 395 if (mState != MUXER_STATE_INITIALIZED) { 396 throw new IllegalStateException("Muxer is not initialized."); 397 } 398 if (mNativeObject == 0) { 399 throw new IllegalStateException("Muxer has been released!"); 400 } 401 int trackIndex = -1; 402 // Convert the MediaFormat into key-value pairs and send to the native. 403 Map<String, Object> formatMap = format.getMap(); 404 405 String[] keys = null; 406 Object[] values = null; 407 int mapSize = formatMap.size(); 408 if (mapSize > 0) { 409 keys = new String[mapSize]; 410 values = new Object[mapSize]; 411 int i = 0; 412 for (Map.Entry<String, Object> entry : formatMap.entrySet()) { 413 keys[i] = entry.getKey(); 414 values[i] = entry.getValue(); 415 ++i; 416 } 417 trackIndex = nativeAddTrack(mNativeObject, keys, values); 418 } else { 419 throw new IllegalArgumentException("format must not be empty."); 420 } 421 422 // Track index number is expected to incremented as addTrack succeed. 423 // However, if format is invalid, it will get a negative trackIndex. 424 if (mLastTrackIndex >= trackIndex) { 425 throw new IllegalArgumentException("Invalid format."); 426 } 427 mLastTrackIndex = trackIndex; 428 return trackIndex; 429 } 430 431 /** 432 * Writes an encoded sample into the muxer. 433 * <p>The application needs to make sure that the samples are written into 434 * the right tracks. Also, it needs to make sure the samples for each track 435 * are written in chronological order (e.g. in the order they are provided 436 * by the encoder.)</p> 437 * @param byteBuf The encoded sample. 438 * @param trackIndex The track index for this sample. 439 * @param bufferInfo The buffer information related to this sample. 440 * @throws IllegalArgumentException if trackIndex, byteBuf or bufferInfo is invalid. 441 * @throws IllegalStateException if muxer is in wrong state. 442 * MediaMuxer uses the flags provided in {@link MediaCodec.BufferInfo}, 443 * to signal sync frames. 444 */ 445 public void writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf, 446 @NonNull BufferInfo bufferInfo) { 447 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 448 throw new IllegalArgumentException("trackIndex is invalid"); 449 } 450 451 if (byteBuf == null) { 452 throw new IllegalArgumentException("byteBuffer must not be null"); 453 } 454 455 if (bufferInfo == null) { 456 throw new IllegalArgumentException("bufferInfo must not be null"); 457 } 458 if (bufferInfo.size < 0 || bufferInfo.offset < 0 459 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 460 || bufferInfo.presentationTimeUs < 0) { 461 throw new IllegalArgumentException("bufferInfo must specify a" + 462 " valid buffer offset, size and presentation time"); 463 } 464 465 if (mNativeObject == 0) { 466 throw new IllegalStateException("Muxer has been released!"); 467 } 468 469 if (mState != MUXER_STATE_STARTED) { 470 throw new IllegalStateException("Can't write, muxer is not started"); 471 } 472 473 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 474 bufferInfo.offset, bufferInfo.size, 475 bufferInfo.presentationTimeUs, bufferInfo.flags); 476 } 477 478 /** 479 * Make sure you call this when you're done to free up any resources 480 * instead of relying on the garbage collector to do this for you at 481 * some point in the future. 482 */ 483 public void release() { 484 if (mState == MUXER_STATE_STARTED) { 485 stop(); 486 } 487 if (mNativeObject != 0) { 488 nativeRelease(mNativeObject); 489 mNativeObject = 0; 490 mCloseGuard.close(); 491 } 492 mState = MUXER_STATE_UNINITIALIZED; 493 } 494} 495