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 17 package android.media; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.media.MediaCodec; 23 import android.media.MediaCodec.BufferInfo; 24 import dalvik.system.CloseGuard; 25 26 import java.io.FileDescriptor; 27 import java.io.IOException; 28 import java.io.RandomAccessFile; 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.nio.ByteBuffer; 32 import java.util.Map; 33 34 /** 35 * MediaMuxer facilitates muxing elementary streams. Currently MediaMuxer supports MP4, Webm 36 * and 3GP file as the output. It also supports muxing B-frames in MP4 since Android Nougat. 37 * <p> 38 * It is generally used like this: 39 * 40 * <pre> 41 * MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4); 42 * // More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat() 43 * // or MediaExtractor.getTrackFormat(). 44 * MediaFormat audioFormat = new MediaFormat(...); 45 * MediaFormat videoFormat = new MediaFormat(...); 46 * int audioTrackIndex = muxer.addTrack(audioFormat); 47 * int videoTrackIndex = muxer.addTrack(videoFormat); 48 * ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize); 49 * boolean finished = false; 50 * BufferInfo bufferInfo = new BufferInfo(); 51 * 52 * muxer.start(); 53 * while(!finished) { 54 * // getInputBuffer() will fill the inputBuffer with one frame of encoded 55 * // sample from either MediaCodec or MediaExtractor, set isAudioSample to 56 * // true when the sample is audio data, set up all the fields of bufferInfo, 57 * // and return true if there are no more samples. 58 * finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo); 59 * if (!finished) { 60 * int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex; 61 * muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo); 62 * } 63 * }; 64 * muxer.stop(); 65 * muxer.release(); 66 * </pre> 67 * 68 69 <h4>Metadata Track</h4> 70 <p> 71 Per-frame metadata is useful in carrying extra information that correlated with video or audio to 72 facilitate offline processing, e.g. gyro signals from the sensor could help video stabilization when 73 doing offline processing. Metadata track is only supported in MP4 container. When adding a new 74 metadata track, track's mime format must start with prefix "application/", e.g. "applicaton/gyro". 75 Metadata's format/layout will be defined by the application. Writing metadata is nearly the same as 76 writing video/audio data except that the data will not be from mediacodec. Application just needs 77 to pass the bytebuffer that contains the metadata and also the associated timestamp to the 78 {@link #writeSampleData} api. The timestamp must be in the same time base as video and audio. The 79 generated MP4 file uses TextMetaDataSampleEntry defined in section 12.3.3.2 of the ISOBMFF to signal 80 the metadata's mime format. When using{@link android.media.MediaExtractor} to extract the file with 81 metadata track, the mime format of the metadata will be extracted into {@link android.media.MediaFormat}. 82 83 <pre class=prettyprint> 84 MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4); 85 // SetUp Video/Audio Tracks. 86 MediaFormat audioFormat = new MediaFormat(...); 87 MediaFormat videoFormat = new MediaFormat(...); 88 int audioTrackIndex = muxer.addTrack(audioFormat); 89 int videoTrackIndex = muxer.addTrack(videoFormat); 90 91 // Setup Metadata Track 92 MediaFormat metadataFormat = new MediaFormat(...); 93 metadataFormat.setString(KEY_MIME, "application/gyro"); 94 int metadataTrackIndex = muxer.addTrack(metadataFormat); 95 96 muxer.start(); 97 while(..) { 98 // Allocate bytebuffer and write gyro data(x,y,z) into it. 99 ByteBuffer metaData = ByteBuffer.allocate(bufferSize); 100 metaData.putFloat(x); 101 metaData.putFloat(y); 102 metaData.putFloat(z); 103 BufferInfo metaInfo = new BufferInfo(); 104 // Associate this metadata with the video frame by setting 105 // the same timestamp as the video frame. 106 metaInfo.presentationTimeUs = currentVideoTrackTimeUs; 107 metaInfo.offset = 0; 108 metaInfo.flags = 0; 109 metaInfo.size = bufferSize; 110 muxer.writeSampleData(metadataTrackIndex, metaData, metaInfo); 111 }; 112 muxer.stop(); 113 muxer.release(); 114 }</pre> 115 116 <h2 id=History><a name="History"></a>Features and API History</h2> 117 <p> 118 The following table summarizes the feature support in different API version and containers. 119 For API version numbers, see {@link android.os.Build.VERSION_CODES}. 120 121 <style> 122 .api > tr > th, .api > tr > td { text-align: center; padding: 4px 4px; } 123 .api > tr > th { vertical-align: bottom; } 124 .api > tr > td { vertical-align: middle; } 125 .sml > tr > th, .sml > tr > td { text-align: center; padding: 2px 4px; } 126 .fn { text-align: center; } 127 </style> 128 129 <table align="right" style="width: 0%"> 130 <thead> 131 <tbody class=api> 132 <tr><th>Symbol</th> 133 <th>Meaning</th></tr> 134 </tbody> 135 </thead> 136 <tbody class=sml> 137 <tr><td>●</td><td>Supported</td></tr> 138 <tr><td>○</td><td>Not supported</td></tr> 139 <tr><td>▧</td><td>Supported in MP4/WebM/3GP</td></tr> 140 <tr><td>⁕</td><td>Only Supported in MP4</td></tr> 141 </tbody> 142 </table> 143 <table align="center" style="width: 100%;"> 144 <thead class=api> 145 <tr> 146 <th rowspan=2>Feature</th> 147 <th colspan="24">SDK Version</th> 148 </tr> 149 <tr> 150 <th>18</th> 151 <th>19</th> 152 <th>20</th> 153 <th>21</th> 154 <th>22</th> 155 <th>23</th> 156 <th>24</th> 157 <th>25</th> 158 <th>26+</th> 159 </tr> 160 </thead> 161 <tbody class=api> 162 <tr> 163 <td align="center">MP4 container</td> 164 <td>●</td> 165 <td>●</td> 166 <td>●</td> 167 <td>●</td> 168 <td>●</td> 169 <td>●</td> 170 <td>●</td> 171 <td>●</td> 172 <td>●</td> 173 </tr> 174 <td align="center">WebM container</td> 175 <td>○</td> 176 <td>○</td> 177 <td>○</td> 178 <td>●</td> 179 <td>●</td> 180 <td>●</td> 181 <td>●</td> 182 <td>●</td> 183 <td>●</td> 184 </tr> 185 <td align="center">3GP container</td> 186 <td>○</td> 187 <td>○</td> 188 <td>○</td> 189 <td>○</td> 190 <td>○</td> 191 <td>○</td> 192 <td>○</td> 193 <td>○</td> 194 <td>●</td> 195 </tr> 196 <td align="center">Muxing B-Frames(bi-directional predicted frames)</td> 197 <td>○</td> 198 <td>○</td> 199 <td>○</td> 200 <td>○</td> 201 <td>○</td> 202 <td>○</td> 203 <td>⁕</td> 204 <td>⁕</td> 205 <td>⁕</td> 206 </tr> 207 </tr> 208 <td align="center">Muxing Single Video/Audio Track</td> 209 <td>▧</td> 210 <td>▧</td> 211 <td>▧</td> 212 <td>▧</td> 213 <td>▧</td> 214 <td>▧</td> 215 <td>▧</td> 216 <td>▧</td> 217 <td>▧</td> 218 </tr> 219 </tr> 220 <td align="center">Muxing Multiple Video/Audio Tracks</td> 221 <td>○</td> 222 <td>○</td> 223 <td>○</td> 224 <td>○</td> 225 <td>○</td> 226 <td>○</td> 227 <td>○</td> 228 <td>○</td> 229 <td>⁕</td> 230 </tr> 231 </tr> 232 <td align="center">Muxing Metadata Tracks</td> 233 <td>○</td> 234 <td>○</td> 235 <td>○</td> 236 <td>○</td> 237 <td>○</td> 238 <td>○</td> 239 <td>○</td> 240 <td>○</td> 241 <td>⁕</td> 242 </tr> 243 </tbody> 244 </table> 245 */ 246 247 final public class MediaMuxer { 248 249 static { 250 System.loadLibrary("media_jni"); 251 } 252 253 /** 254 * Defines the output format. These constants are used with constructor. 255 */ 256 public static final class OutputFormat { 257 /* Do not change these values without updating their counterparts 258 * in include/media/stagefright/MediaMuxer.h! 259 */ OutputFormat()260 private OutputFormat() {} 261 /** @hide */ 262 public static final int MUXER_OUTPUT_FIRST = 0; 263 /** MPEG4 media file format*/ 264 public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST; 265 /** WEBM media file format*/ 266 public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1; 267 /** 3GPP media file format*/ 268 public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2; 269 /** HEIF media file format*/ 270 public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3; 271 /** @hide */ 272 public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF; 273 }; 274 275 /** @hide */ 276 @IntDef({ 277 OutputFormat.MUXER_OUTPUT_MPEG_4, 278 OutputFormat.MUXER_OUTPUT_WEBM, 279 OutputFormat.MUXER_OUTPUT_3GPP, 280 OutputFormat.MUXER_OUTPUT_HEIF, 281 }) 282 @Retention(RetentionPolicy.SOURCE) 283 public @interface Format {} 284 285 // All the native functions are listed here. nativeSetup(@onNull FileDescriptor fd, int format)286 private static native long nativeSetup(@NonNull FileDescriptor fd, int format) 287 throws IllegalArgumentException, IOException; nativeRelease(long nativeObject)288 private static native void nativeRelease(long nativeObject); nativeStart(long nativeObject)289 private static native void nativeStart(long nativeObject); nativeStop(long nativeObject)290 private static native void nativeStop(long nativeObject); nativeAddTrack( long nativeObject, @NonNull String[] keys, @NonNull Object[] values)291 private static native int nativeAddTrack( 292 long nativeObject, @NonNull String[] keys, @NonNull Object[] values); nativeSetOrientationHint( long nativeObject, int degrees)293 private static native void nativeSetOrientationHint( 294 long nativeObject, int degrees); nativeSetLocation(long nativeObject, int latitude, int longitude)295 private static native void nativeSetLocation(long nativeObject, int latitude, int longitude); nativeWriteSampleData( long nativeObject, int trackIndex, @NonNull ByteBuffer byteBuf, int offset, int size, long presentationTimeUs, @MediaCodec.BufferFlag int flags)296 private static native void nativeWriteSampleData( 297 long nativeObject, int trackIndex, @NonNull ByteBuffer byteBuf, 298 int offset, int size, long presentationTimeUs, @MediaCodec.BufferFlag int flags); 299 300 // Muxer internal states. 301 private static final int MUXER_STATE_UNINITIALIZED = -1; 302 private static final int MUXER_STATE_INITIALIZED = 0; 303 private static final int MUXER_STATE_STARTED = 1; 304 private static final int MUXER_STATE_STOPPED = 2; 305 306 private int mState = MUXER_STATE_UNINITIALIZED; 307 308 private final CloseGuard mCloseGuard = CloseGuard.get(); 309 private int mLastTrackIndex = -1; 310 311 private long mNativeObject; 312 313 /** 314 * Constructor. 315 * Creates a media muxer that writes to the specified path. 316 * @param path The path of the output media file. 317 * @param format The format of the output media file. 318 * @see android.media.MediaMuxer.OutputFormat 319 * @throws IllegalArgumentException if path is invalid or format is not supported. 320 * @throws IOException if failed to open the file for write. 321 */ MediaMuxer(@onNull String path, @Format int format)322 public MediaMuxer(@NonNull String path, @Format int format) throws IOException { 323 if (path == null) { 324 throw new IllegalArgumentException("path must not be null"); 325 } 326 // Use RandomAccessFile so we can open the file with RW access; 327 // RW access allows the native writer to memory map the output file. 328 RandomAccessFile file = null; 329 try { 330 file = new RandomAccessFile(path, "rws"); 331 file.setLength(0); 332 FileDescriptor fd = file.getFD(); 333 setUpMediaMuxer(fd, format); 334 } finally { 335 if (file != null) { 336 file.close(); 337 } 338 } 339 } 340 341 /** 342 * Constructor. 343 * Creates a media muxer that writes to the specified FileDescriptor. File descriptor 344 * must be seekable and writable. Application should not use the file referenced 345 * by this file descriptor until {@link #stop}. It is the application's responsibility 346 * to close the file descriptor. It is safe to do so as soon as this call returns. 347 * @param fd The FileDescriptor of the output media file. 348 * @param format The format of the output media file. 349 * @see android.media.MediaMuxer.OutputFormat 350 * @throws IllegalArgumentException if fd is invalid or format is not supported. 351 * @throws IOException if failed to open the file for write. 352 */ MediaMuxer(@onNull FileDescriptor fd, @Format int format)353 public MediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException { 354 setUpMediaMuxer(fd, format); 355 } 356 setUpMediaMuxer(@onNull FileDescriptor fd, @Format int format)357 private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException { 358 if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) { 359 throw new IllegalArgumentException("format: " + format + " is invalid"); 360 } 361 mNativeObject = nativeSetup(fd, format); 362 mState = MUXER_STATE_INITIALIZED; 363 mCloseGuard.open("release"); 364 } 365 366 /** 367 * Sets the orientation hint for output video playback. 368 * <p>This method should be called before {@link #start}. Calling this 369 * method will not rotate the video frame when muxer is generating the file, 370 * but add a composition matrix containing the rotation angle in the output 371 * video if the output format is 372 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4} so that a video player can 373 * choose the proper orientation for playback. Note that some video players 374 * may choose to ignore the composition matrix in a video during playback. 375 * By default, the rotation degree is 0.</p> 376 * @param degrees the angle to be rotated clockwise in degrees. 377 * The supported angles are 0, 90, 180, and 270 degrees. 378 * @throws IllegalArgumentException if degree is not supported. 379 * @throws IllegalStateException If this method is called after {@link #start}. 380 */ setOrientationHint(int degrees)381 public void setOrientationHint(int degrees) { 382 if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { 383 throw new IllegalArgumentException("Unsupported angle: " + degrees); 384 } 385 if (mState == MUXER_STATE_INITIALIZED) { 386 nativeSetOrientationHint(mNativeObject, degrees); 387 } else { 388 throw new IllegalStateException("Can't set rotation degrees due" + 389 " to wrong state."); 390 } 391 } 392 393 /** 394 * Set and store the geodata (latitude and longitude) in the output file. 395 * This method should be called before {@link #start}. The geodata is stored 396 * in udta box if the output format is 397 * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output 398 * formats. The geodata is stored according to ISO-6709 standard. 399 * 400 * @param latitude Latitude in degrees. Its value must be in the range [-90, 401 * 90]. 402 * @param longitude Longitude in degrees. Its value must be in the range 403 * [-180, 180]. 404 * @throws IllegalArgumentException If the given latitude or longitude is out 405 * of range. 406 * @throws IllegalStateException If this method is called after {@link #start}. 407 */ setLocation(float latitude, float longitude)408 public void setLocation(float latitude, float longitude) { 409 int latitudex10000 = (int) (latitude * 10000 + 0.5); 410 int longitudex10000 = (int) (longitude * 10000 + 0.5); 411 412 if (latitudex10000 > 900000 || latitudex10000 < -900000) { 413 String msg = "Latitude: " + latitude + " out of range."; 414 throw new IllegalArgumentException(msg); 415 } 416 if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { 417 String msg = "Longitude: " + longitude + " out of range"; 418 throw new IllegalArgumentException(msg); 419 } 420 421 if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { 422 nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); 423 } else { 424 throw new IllegalStateException("Can't set location due to wrong state."); 425 } 426 } 427 428 /** 429 * Starts the muxer. 430 * <p>Make sure this is called after {@link #addTrack} and before 431 * {@link #writeSampleData}.</p> 432 * @throws IllegalStateException If this method is called after {@link #start} 433 * or Muxer is released 434 */ start()435 public void start() { 436 if (mNativeObject == 0) { 437 throw new IllegalStateException("Muxer has been released!"); 438 } 439 if (mState == MUXER_STATE_INITIALIZED) { 440 nativeStart(mNativeObject); 441 mState = MUXER_STATE_STARTED; 442 } else { 443 throw new IllegalStateException("Can't start due to wrong state."); 444 } 445 } 446 447 /** 448 * Stops the muxer. 449 * <p>Once the muxer stops, it can not be restarted.</p> 450 * @throws IllegalStateException if muxer is in the wrong state. 451 */ stop()452 public void stop() { 453 if (mState == MUXER_STATE_STARTED) { 454 nativeStop(mNativeObject); 455 mState = MUXER_STATE_STOPPED; 456 } else { 457 throw new IllegalStateException("Can't stop due to wrong state."); 458 } 459 } 460 461 @Override finalize()462 protected void finalize() throws Throwable { 463 try { 464 if (mCloseGuard != null) { 465 mCloseGuard.warnIfOpen(); 466 } 467 if (mNativeObject != 0) { 468 nativeRelease(mNativeObject); 469 mNativeObject = 0; 470 } 471 } finally { 472 super.finalize(); 473 } 474 } 475 476 /** 477 * Adds a track with the specified format. 478 * <p> 479 * The following table summarizes support for specific format keys across android releases. 480 * Keys marked with '+:' are required. 481 * 482 * <table style="width: 0%"> 483 * <thead> 484 * <tr> 485 * <th rowspan=2>OS Version(s)</th> 486 * <td colspan=3>{@code MediaFormat} keys used for</th> 487 * </tr><tr> 488 * <th>All Tracks</th> 489 * <th>Audio Tracks</th> 490 * <th>Video Tracks</th> 491 * </tr> 492 * </thead> 493 * <tbody> 494 * <tr> 495 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> 496 * <td rowspan=7>+: {@link MediaFormat#KEY_MIME}</td> 497 * <td rowspan=3>+: {@link MediaFormat#KEY_SAMPLE_RATE},<br> 498 * +: {@link MediaFormat#KEY_CHANNEL_COUNT},<br> 499 * +: <strong>codec-specific data<sup>AAC</sup></strong></td> 500 * <td rowspan=5>+: {@link MediaFormat#KEY_WIDTH},<br> 501 * +: {@link MediaFormat#KEY_HEIGHT},<br> 502 * no {@code KEY_ROTATION}, 503 * use {@link #setOrientationHint setOrientationHint()}<sup>.mp4</sup>,<br> 504 * +: <strong>codec-specific data<sup>AVC, MPEG4</sup></strong></td> 505 * </tr><tr> 506 * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> 507 * </tr><tr> 508 * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> 509 * </tr><tr> 510 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> 511 * <td rowspan=4>as above, plus<br> 512 * +: <strong>codec-specific data<sup>Vorbis & .webm</sup></strong></td> 513 * </tr><tr> 514 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> 515 * </tr><tr> 516 * <td>{@link android.os.Build.VERSION_CODES#M}</td> 517 * <td>as above, plus<br> 518 * {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup></td> 519 * </tr><tr> 520 * <td>{@link android.os.Build.VERSION_CODES#N}</td> 521 * <td>as above, plus<br> 522 * <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>AAC, MPEG4</sup>,<br> --> 523 * {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br> 524 * {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br> 525 * {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br> 526 * {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br> 527 * {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br> 528 * +: <strong>codec-specific data<sup>HEVC</sup></strong>,<br> 529 * codec-specific data<sup>VP9</sup></td> 530 * </tr> 531 * <tr> 532 * <td colspan=4> 533 * <p class=note><strong>Notes:</strong><br> 534 * #: storing into container metadata.<br> 535 * .mp4, .webm…: for listed containers<br> 536 * MPEG4, AAC…: for listed codecs 537 * </td> 538 * </tr><tr> 539 * <td colspan=4> 540 * <p class=note>Note that the codec-specific data for the track must be specified using 541 * this method. Furthermore, codec-specific data must not be passed/specified via the 542 * {@link #writeSampleData writeSampleData()} call. 543 * </td> 544 * </tr> 545 * </tbody> 546 * </table> 547 * 548 * <p> 549 * The following table summarizes codec support for containers across android releases: 550 * 551 * <table style="width: 0%"> 552 * <thead> 553 * <tr> 554 * <th rowspan=2>OS Version(s)</th> 555 * <td colspan=3>Codec support</th> 556 * </tr><tr> 557 * <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th> 558 * <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th> 559 * </tr> 560 * </thead> 561 * <tbody> 562 * <tr> 563 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> 564 * <td rowspan=6>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC},<br> 565 * {@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR},<br> 566 * {@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR},<br> 567 * {@link MediaFormat#MIMETYPE_VIDEO_H263 H.263},<br> 568 * {@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4},<br> 569 * {@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td> 570 * <td rowspan=3>Not supported</td> 571 * </tr><tr> 572 * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> 573 * </tr><tr> 574 * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> 575 * </tr><tr> 576 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> 577 * <td rowspan=3>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis},<br> 578 * {@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td> 579 * </tr><tr> 580 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> 581 * </tr><tr> 582 * <td>{@link android.os.Build.VERSION_CODES#M}</td> 583 * </tr><tr> 584 * <td>{@link android.os.Build.VERSION_CODES#N}</td> 585 * <td>as above, plus<br> 586 * {@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td> 587 * <td>as above, plus<br> 588 * {@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td> 589 * </tr> 590 * </tbody> 591 * </table> 592 * 593 * @param format The media format for the track. This must not be an empty 594 * MediaFormat. 595 * @return The track index for this newly added track, and it should be used 596 * in the {@link #writeSampleData}. 597 * @throws IllegalArgumentException if format is invalid. 598 * @throws IllegalStateException if muxer is in the wrong state. 599 */ addTrack(@onNull MediaFormat format)600 public int addTrack(@NonNull MediaFormat format) { 601 if (format == null) { 602 throw new IllegalArgumentException("format must not be null."); 603 } 604 if (mState != MUXER_STATE_INITIALIZED) { 605 throw new IllegalStateException("Muxer is not initialized."); 606 } 607 if (mNativeObject == 0) { 608 throw new IllegalStateException("Muxer has been released!"); 609 } 610 int trackIndex = -1; 611 // Convert the MediaFormat into key-value pairs and send to the native. 612 Map<String, Object> formatMap = format.getMap(); 613 614 String[] keys = null; 615 Object[] values = null; 616 int mapSize = formatMap.size(); 617 if (mapSize > 0) { 618 keys = new String[mapSize]; 619 values = new Object[mapSize]; 620 int i = 0; 621 for (Map.Entry<String, Object> entry : formatMap.entrySet()) { 622 keys[i] = entry.getKey(); 623 values[i] = entry.getValue(); 624 ++i; 625 } 626 trackIndex = nativeAddTrack(mNativeObject, keys, values); 627 } else { 628 throw new IllegalArgumentException("format must not be empty."); 629 } 630 631 // Track index number is expected to incremented as addTrack succeed. 632 // However, if format is invalid, it will get a negative trackIndex. 633 if (mLastTrackIndex >= trackIndex) { 634 throw new IllegalArgumentException("Invalid format."); 635 } 636 mLastTrackIndex = trackIndex; 637 return trackIndex; 638 } 639 640 /** 641 * Writes an encoded sample into the muxer. 642 * <p>The application needs to make sure that the samples are written into 643 * the right tracks. Also, it needs to make sure the samples for each track 644 * are written in chronological order (e.g. in the order they are provided 645 * by the encoder.)</p> 646 * @param byteBuf The encoded sample. 647 * @param trackIndex The track index for this sample. 648 * @param bufferInfo The buffer information related to this sample. 649 * @throws IllegalArgumentException if trackIndex, byteBuf or bufferInfo is invalid. 650 * @throws IllegalStateException if muxer is in wrong state. 651 * MediaMuxer uses the flags provided in {@link MediaCodec.BufferInfo}, 652 * to signal sync frames. 653 */ writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf, @NonNull BufferInfo bufferInfo)654 public void writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf, 655 @NonNull BufferInfo bufferInfo) { 656 if (trackIndex < 0 || trackIndex > mLastTrackIndex) { 657 throw new IllegalArgumentException("trackIndex is invalid"); 658 } 659 660 if (byteBuf == null) { 661 throw new IllegalArgumentException("byteBuffer must not be null"); 662 } 663 664 if (bufferInfo == null) { 665 throw new IllegalArgumentException("bufferInfo must not be null"); 666 } 667 if (bufferInfo.size < 0 || bufferInfo.offset < 0 668 || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity() 669 || bufferInfo.presentationTimeUs < 0) { 670 throw new IllegalArgumentException("bufferInfo must specify a" + 671 " valid buffer offset, size and presentation time"); 672 } 673 674 if (mNativeObject == 0) { 675 throw new IllegalStateException("Muxer has been released!"); 676 } 677 678 if (mState != MUXER_STATE_STARTED) { 679 throw new IllegalStateException("Can't write, muxer is not started"); 680 } 681 682 nativeWriteSampleData(mNativeObject, trackIndex, byteBuf, 683 bufferInfo.offset, bufferInfo.size, 684 bufferInfo.presentationTimeUs, bufferInfo.flags); 685 } 686 687 /** 688 * Make sure you call this when you're done to free up any resources 689 * instead of relying on the garbage collector to do this for you at 690 * some point in the future. 691 */ release()692 public void release() { 693 if (mState == MUXER_STATE_STARTED) { 694 stop(); 695 } 696 if (mNativeObject != 0) { 697 nativeRelease(mNativeObject); 698 mNativeObject = 0; 699 mCloseGuard.close(); 700 } 701 mState = MUXER_STATE_UNINITIALIZED; 702 } 703 } 704