1/* 2 * Copyright (C) 2012 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.content.ContentResolver; 23import android.content.Context; 24import android.content.res.AssetFileDescriptor; 25import android.media.MediaCodec; 26import android.media.MediaFormat; 27import android.media.MediaHTTPService; 28import android.net.Uri; 29import android.os.IBinder; 30 31import com.android.internal.util.Preconditions; 32 33import java.io.FileDescriptor; 34import java.io.IOException; 35import java.lang.annotation.Retention; 36import java.lang.annotation.RetentionPolicy; 37import java.nio.ByteBuffer; 38import java.nio.ByteOrder; 39import java.util.Collections; 40import java.util.HashMap; 41import java.util.List; 42import java.util.Map; 43import java.util.UUID; 44 45/** 46 * MediaExtractor facilitates extraction of demuxed, typically encoded, media data 47 * from a data source. 48 * <p>It is generally used like this: 49 * <pre> 50 * MediaExtractor extractor = new MediaExtractor(); 51 * extractor.setDataSource(...); 52 * int numTracks = extractor.getTrackCount(); 53 * for (int i = 0; i < numTracks; ++i) { 54 * MediaFormat format = extractor.getTrackFormat(i); 55 * String mime = format.getString(MediaFormat.KEY_MIME); 56 * if (weAreInterestedInThisTrack) { 57 * extractor.selectTrack(i); 58 * } 59 * } 60 * ByteBuffer inputBuffer = ByteBuffer.allocate(...) 61 * while (extractor.readSampleData(inputBuffer, ...) >= 0) { 62 * int trackIndex = extractor.getSampleTrackIndex(); 63 * long presentationTimeUs = extractor.getSampleTime(); 64 * ... 65 * extractor.advance(); 66 * } 67 * 68 * extractor.release(); 69 * extractor = null; 70 * </pre> 71 */ 72final public class MediaExtractor { 73 public MediaExtractor() { 74 native_setup(); 75 } 76 77 /** 78 * Sets the data source (MediaDataSource) to use. 79 * 80 * @param dataSource the MediaDataSource for the media you want to extract from 81 * 82 * @throws IllegalArgumentException if dataSource is invalid. 83 */ 84 public native final void setDataSource(@NonNull MediaDataSource dataSource) 85 throws IOException; 86 87 /** 88 * Sets the data source as a content Uri. 89 * 90 * @param context the Context to use when resolving the Uri 91 * @param uri the Content URI of the data you want to extract from. 92 * @param headers the headers to be sent together with the request for the data. 93 * This can be {@code null} if no specific headers are to be sent with the 94 * request. 95 */ 96 public final void setDataSource( 97 @NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers) 98 throws IOException { 99 String scheme = uri.getScheme(); 100 if (scheme == null || scheme.equals("file")) { 101 setDataSource(uri.getPath()); 102 return; 103 } 104 105 AssetFileDescriptor fd = null; 106 try { 107 ContentResolver resolver = context.getContentResolver(); 108 fd = resolver.openAssetFileDescriptor(uri, "r"); 109 if (fd == null) { 110 return; 111 } 112 // Note: using getDeclaredLength so that our behavior is the same 113 // as previous versions when the content provider is returning 114 // a full file. 115 if (fd.getDeclaredLength() < 0) { 116 setDataSource(fd.getFileDescriptor()); 117 } else { 118 setDataSource( 119 fd.getFileDescriptor(), 120 fd.getStartOffset(), 121 fd.getDeclaredLength()); 122 } 123 return; 124 } catch (SecurityException ex) { 125 } catch (IOException ex) { 126 } finally { 127 if (fd != null) { 128 fd.close(); 129 } 130 } 131 132 setDataSource(uri.toString(), headers); 133 } 134 135 /** 136 * Sets the data source (file-path or http URL) to use. 137 * 138 * @param path the path of the file, or the http URL 139 * @param headers the headers associated with the http request for the stream you want to play. 140 * This can be {@code null} if no specific headers are to be sent with the 141 * request. 142 */ 143 public final void setDataSource(@NonNull String path, @Nullable Map<String, String> headers) 144 throws IOException { 145 String[] keys = null; 146 String[] values = null; 147 148 if (headers != null) { 149 keys = new String[headers.size()]; 150 values = new String[headers.size()]; 151 152 int i = 0; 153 for (Map.Entry<String, String> entry: headers.entrySet()) { 154 keys[i] = entry.getKey(); 155 values[i] = entry.getValue(); 156 ++i; 157 } 158 } 159 160 nativeSetDataSource( 161 MediaHTTPService.createHttpServiceBinderIfNecessary(path), 162 path, 163 keys, 164 values); 165 } 166 167 private native final void nativeSetDataSource( 168 @NonNull IBinder httpServiceBinder, 169 @NonNull String path, 170 @Nullable String[] keys, 171 @Nullable String[] values) throws IOException; 172 173 /** 174 * Sets the data source (file-path or http URL) to use. 175 * 176 * @param path the path of the file, or the http URL of the stream 177 * 178 * <p>When <code>path</code> refers to a local file, the file may actually be opened by a 179 * process other than the calling application. This implies that the pathname 180 * should be an absolute path (as any other process runs with unspecified current working 181 * directory), and that the pathname should reference a world-readable file. 182 * As an alternative, the application could first open the file for reading, 183 * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}. 184 */ 185 public final void setDataSource(@NonNull String path) throws IOException { 186 nativeSetDataSource( 187 MediaHTTPService.createHttpServiceBinderIfNecessary(path), 188 path, 189 null, 190 null); 191 } 192 193 /** 194 * Sets the data source (AssetFileDescriptor) to use. It is the caller's 195 * responsibility to close the file descriptor. It is safe to do so as soon 196 * as this call returns. 197 * 198 * @param afd the AssetFileDescriptor for the file you want to extract from. 199 */ 200 public final void setDataSource(@NonNull AssetFileDescriptor afd) 201 throws IOException, IllegalArgumentException, IllegalStateException { 202 Preconditions.checkNotNull(afd); 203 // Note: using getDeclaredLength so that our behavior is the same 204 // as previous versions when the content provider is returning 205 // a full file. 206 if (afd.getDeclaredLength() < 0) { 207 setDataSource(afd.getFileDescriptor()); 208 } else { 209 setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength()); 210 } 211 } 212 213 /** 214 * Sets the data source (FileDescriptor) to use. It is the caller's responsibility 215 * to close the file descriptor. It is safe to do so as soon as this call returns. 216 * 217 * @param fd the FileDescriptor for the file you want to extract from. 218 */ 219 public final void setDataSource(@NonNull FileDescriptor fd) throws IOException { 220 setDataSource(fd, 0, 0x7ffffffffffffffL); 221 } 222 223 /** 224 * Sets the data source (FileDescriptor) to use. The FileDescriptor must be 225 * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility 226 * to close the file descriptor. It is safe to do so as soon as this call returns. 227 * 228 * @param fd the FileDescriptor for the file you want to extract from. 229 * @param offset the offset into the file where the data to be extracted starts, in bytes 230 * @param length the length in bytes of the data to be extracted 231 */ 232 public native final void setDataSource( 233 @NonNull FileDescriptor fd, long offset, long length) throws IOException; 234 235 @Override 236 protected void finalize() { 237 native_finalize(); 238 } 239 240 /** 241 * Make sure you call this when you're done to free up any resources 242 * instead of relying on the garbage collector to do this for you at 243 * some point in the future. 244 */ 245 public native final void release(); 246 247 /** 248 * Count the number of tracks found in the data source. 249 */ 250 public native final int getTrackCount(); 251 252 /** 253 * Extract DRM initialization data if it exists 254 * 255 * @return DRM initialization data in the content, or {@code null} 256 * if no recognizable DRM format is found; 257 * @see DrmInitData 258 */ 259 public DrmInitData getDrmInitData() { 260 Map<String, Object> formatMap = getFileFormatNative(); 261 if (formatMap == null) { 262 return null; 263 } 264 if (formatMap.containsKey("pssh")) { 265 Map<UUID, byte[]> psshMap = getPsshInfo(); 266 final Map<UUID, DrmInitData.SchemeInitData> initDataMap = 267 new HashMap<UUID, DrmInitData.SchemeInitData>(); 268 for (Map.Entry<UUID, byte[]> e: psshMap.entrySet()) { 269 UUID uuid = e.getKey(); 270 byte[] data = e.getValue(); 271 initDataMap.put(uuid, new DrmInitData.SchemeInitData("cenc", data)); 272 } 273 return new DrmInitData() { 274 public SchemeInitData get(UUID schemeUuid) { 275 return initDataMap.get(schemeUuid); 276 } 277 }; 278 } else { 279 int numTracks = getTrackCount(); 280 for (int i = 0; i < numTracks; ++i) { 281 Map<String, Object> trackFormatMap = getTrackFormatNative(i); 282 if (!trackFormatMap.containsKey("crypto-key")) { 283 continue; 284 } 285 ByteBuffer buf = (ByteBuffer) trackFormatMap.get("crypto-key"); 286 buf.rewind(); 287 final byte[] data = new byte[buf.remaining()]; 288 buf.get(data); 289 return new DrmInitData() { 290 public SchemeInitData get(UUID schemeUuid) { 291 return new DrmInitData.SchemeInitData("webm", data); 292 } 293 }; 294 } 295 } 296 return null; 297 } 298 299 /** 300 * Get the PSSH info if present. 301 * @return a map of uuid-to-bytes, with the uuid specifying 302 * the crypto scheme, and the bytes being the data specific to that scheme. 303 * This can be {@code null} if the source does not contain PSSH info. 304 */ 305 @Nullable 306 public Map<UUID, byte[]> getPsshInfo() { 307 Map<UUID, byte[]> psshMap = null; 308 Map<String, Object> formatMap = getFileFormatNative(); 309 if (formatMap != null && formatMap.containsKey("pssh")) { 310 ByteBuffer rawpssh = (ByteBuffer) formatMap.get("pssh"); 311 rawpssh.order(ByteOrder.nativeOrder()); 312 rawpssh.rewind(); 313 formatMap.remove("pssh"); 314 // parse the flat pssh bytebuffer into something more manageable 315 psshMap = new HashMap<UUID, byte[]>(); 316 while (rawpssh.remaining() > 0) { 317 rawpssh.order(ByteOrder.BIG_ENDIAN); 318 long msb = rawpssh.getLong(); 319 long lsb = rawpssh.getLong(); 320 UUID uuid = new UUID(msb, lsb); 321 rawpssh.order(ByteOrder.nativeOrder()); 322 int datalen = rawpssh.getInt(); 323 byte [] psshdata = new byte[datalen]; 324 rawpssh.get(psshdata); 325 psshMap.put(uuid, psshdata); 326 } 327 } 328 return psshMap; 329 } 330 331 @NonNull 332 private native Map<String, Object> getFileFormatNative(); 333 334 /** 335 * Get the track format at the specified index. 336 * 337 * More detail on the representation can be found at {@link android.media.MediaCodec} 338 * <p> 339 * The following table summarizes support for format keys across android releases: 340 * 341 * <table style="width: 0%"> 342 * <thead> 343 * <tr> 344 * <th rowspan=2>OS Version(s)</th> 345 * <td colspan=3>{@code MediaFormat} keys used for</th> 346 * </tr><tr> 347 * <th>All Tracks</th> 348 * <th>Audio Tracks</th> 349 * <th>Video Tracks</th> 350 * </tr> 351 * </thead> 352 * <tbody> 353 * <tr> 354 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN}</td> 355 * <td rowspan=8>{@link MediaFormat#KEY_MIME},<br> 356 * {@link MediaFormat#KEY_DURATION},<br> 357 * {@link MediaFormat#KEY_MAX_INPUT_SIZE}</td> 358 * <td rowspan=5>{@link MediaFormat#KEY_SAMPLE_RATE},<br> 359 * {@link MediaFormat#KEY_CHANNEL_COUNT},<br> 360 * {@link MediaFormat#KEY_CHANNEL_MASK},<br> 361 * gapless playback information<sup>.mp3, .mp4</sup>,<br> 362 * {@link MediaFormat#KEY_IS_ADTS}<sup>AAC if streaming</sup>,<br> 363 * codec-specific data<sup>AAC, Vorbis</sup></td> 364 * <td rowspan=2>{@link MediaFormat#KEY_WIDTH},<br> 365 * {@link MediaFormat#KEY_HEIGHT},<br> 366 * codec-specific data<sup>AVC, MPEG4</sup></td> 367 * </tr><tr> 368 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}</td> 369 * </tr><tr> 370 * <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td> 371 * <td rowspan=3>as above, plus<br> 372 * Pixel aspect ratio information<sup>AVC, *</sup></td> 373 * </tr><tr> 374 * <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td> 375 * </tr><tr> 376 * <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td> 377 * </tr><tr> 378 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td> 379 * <td rowspan=2>as above, plus<br> 380 * {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup>,<br> 381 * codec-specific data<sup>Opus</sup></td> 382 * <td rowspan=2>as above, plus<br> 383 * {@link MediaFormat#KEY_ROTATION}<sup>.mp4</sup>,<br> 384 * {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br> 385 * codec-specific data<sup>HEVC</sup></td> 386 * </tr><tr> 387 * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td> 388 * </tr><tr> 389 * <td>{@link android.os.Build.VERSION_CODES#M}</td> 390 * <td>as above, plus<br> 391 * gapless playback information<sup>Opus</sup></td> 392 * <td>as above, plus<br> 393 * {@link MediaFormat#KEY_FRAME_RATE} (integer)</td> 394 * </tr><tr> 395 * <td>{@link android.os.Build.VERSION_CODES#N}</td> 396 * <td>as above, plus<br> 397 * {@link MediaFormat#KEY_TRACK_ID},<br> 398 * <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>#, .mp4</sup>,<br> --> 399 * {@link MediaFormat#KEY_BIT_RATE}<sup>#, .mp4</sup></td> 400 * <td>as above, plus<br> 401 * {@link MediaFormat#KEY_PCM_ENCODING},<br> 402 * {@link MediaFormat#KEY_PROFILE}<sup>AAC</sup></td> 403 * <td>as above, plus<br> 404 * {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br> 405 * {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br> 406 * {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br> 407 * {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br> 408 * {@link MediaFormat#KEY_PROFILE}<sup>MPEG2, H.263, MPEG4, AVC, HEVC, VP9</sup>,<br> 409 * {@link MediaFormat#KEY_LEVEL}<sup>H.263, MPEG4, AVC, HEVC, VP9</sup>,<br> 410 * codec-specific data<sup>VP9</sup></td> 411 * </tr> 412 * <tr> 413 * <td colspan=4> 414 * <p class=note><strong>Notes:</strong><br> 415 * #: container-specified value only.<br> 416 * .mp4, .webm…: for listed containers<br> 417 * MPEG4, AAC…: for listed codecs 418 * </td> 419 * </tr><tr> 420 * <td colspan=4> 421 * <p class=note>Note that that level information contained in the container many times 422 * does not match the level of the actual bitstream. You may want to clear the level using 423 * {@code MediaFormat.setString(KEY_LEVEL, null)} before using the track format to find a 424 * decoder that can play back a particular track. 425 * </td> 426 * </tr><tr> 427 * <td colspan=4> 428 * <p class=note><strong>*Pixel (sample) aspect ratio</strong> is returned in the following 429 * keys. The display width can be calculated for example as: 430 * <p align=center> 431 * display-width = display-height * crop-width / crop-height * sar-width / sar-height 432 * </td> 433 * </tr><tr> 434 * <th>Format Key</th><th>Value Type</th><th colspan=2>Description</th> 435 * </tr><tr> 436 * <td>{@code "sar-width"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio width</td> 437 * </tr><tr> 438 * <td>{@code "sar-height"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio height</td> 439 * </tr> 440 * </tbody> 441 * </table> 442 * 443 */ 444 @NonNull 445 public MediaFormat getTrackFormat(int index) { 446 return new MediaFormat(getTrackFormatNative(index)); 447 } 448 449 @NonNull 450 private native Map<String, Object> getTrackFormatNative(int index); 451 452 /** 453 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and 454 * {@link #getSampleTime} only retrieve information for the subset of tracks 455 * selected. 456 * Selecting the same track multiple times has no effect, the track is 457 * only selected once. 458 */ 459 public native void selectTrack(int index); 460 461 /** 462 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and 463 * {@link #getSampleTime} only retrieve information for the subset of tracks 464 * selected. 465 */ 466 public native void unselectTrack(int index); 467 468 /** 469 * If possible, seek to a sync sample at or before the specified time 470 */ 471 public static final int SEEK_TO_PREVIOUS_SYNC = 0; 472 /** 473 * If possible, seek to a sync sample at or after the specified time 474 */ 475 public static final int SEEK_TO_NEXT_SYNC = 1; 476 /** 477 * If possible, seek to the sync sample closest to the specified time 478 */ 479 public static final int SEEK_TO_CLOSEST_SYNC = 2; 480 481 /** @hide */ 482 @IntDef({ 483 SEEK_TO_PREVIOUS_SYNC, 484 SEEK_TO_NEXT_SYNC, 485 SEEK_TO_CLOSEST_SYNC, 486 }) 487 @Retention(RetentionPolicy.SOURCE) 488 public @interface SeekMode {} 489 490 /** 491 * All selected tracks seek near the requested time according to the 492 * specified mode. 493 */ 494 public native void seekTo(long timeUs, @SeekMode int mode); 495 496 /** 497 * Advance to the next sample. Returns false if no more sample data 498 * is available (end of stream). 499 */ 500 public native boolean advance(); 501 502 /** 503 * Retrieve the current encoded sample and store it in the byte buffer 504 * starting at the given offset. 505 * <p> 506 * <b>Note:</b>As of API 21, on success the position and limit of 507 * {@code byteBuf} is updated to point to the data just read. 508 * @param byteBuf the destination byte buffer 509 * @return the sample size (or -1 if no more samples are available). 510 */ 511 public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset); 512 513 /** 514 * Returns the track index the current sample originates from (or -1 515 * if no more samples are available) 516 */ 517 public native int getSampleTrackIndex(); 518 519 /** 520 * Returns the current sample's presentation time in microseconds. 521 * or -1 if no more samples are available. 522 */ 523 public native long getSampleTime(); 524 525 // Keep these in sync with their equivalents in NuMediaExtractor.h 526 /** 527 * The sample is a sync sample (or in {@link MediaCodec}'s terminology 528 * it is a key frame.) 529 * 530 * @see MediaCodec#BUFFER_FLAG_KEY_FRAME 531 */ 532 public static final int SAMPLE_FLAG_SYNC = 1; 533 534 /** 535 * The sample is (at least partially) encrypted, see also the documentation 536 * for {@link android.media.MediaCodec#queueSecureInputBuffer} 537 */ 538 public static final int SAMPLE_FLAG_ENCRYPTED = 2; 539 540 /** @hide */ 541 @IntDef( 542 flag = true, 543 value = { 544 SAMPLE_FLAG_SYNC, 545 SAMPLE_FLAG_ENCRYPTED, 546 }) 547 @Retention(RetentionPolicy.SOURCE) 548 public @interface SampleFlag {} 549 550 /** 551 * Returns the current sample's flags. 552 */ 553 @SampleFlag 554 public native int getSampleFlags(); 555 556 /** 557 * If the sample flags indicate that the current sample is at least 558 * partially encrypted, this call returns relevant information about 559 * the structure of the sample data required for decryption. 560 * @param info The android.media.MediaCodec.CryptoInfo structure 561 * to be filled in. 562 * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED} 563 */ 564 public native boolean getSampleCryptoInfo(@NonNull MediaCodec.CryptoInfo info); 565 566 /** 567 * Returns an estimate of how much data is presently cached in memory 568 * expressed in microseconds. Returns -1 if that information is unavailable 569 * or not applicable (no cache). 570 */ 571 public native long getCachedDuration(); 572 573 /** 574 * Returns true iff we are caching data and the cache has reached the 575 * end of the data stream (for now, a future seek may of course restart 576 * the fetching of data). 577 * This API only returns a meaningful result if {@link #getCachedDuration} 578 * indicates the presence of a cache, i.e. does NOT return -1. 579 */ 580 public native boolean hasCacheReachedEndOfStream(); 581 582 private static native final void native_init(); 583 private native final void native_setup(); 584 private native final void native_finalize(); 585 586 static { 587 System.loadLibrary("media_jni"); 588 native_init(); 589 } 590 591 private long mNativeContext; 592} 593