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 
17 package android.media;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.res.AssetFileDescriptor;
25 import android.media.AudioPresentation;
26 import android.media.MediaCodec;
27 import android.media.MediaFormat;
28 import android.media.MediaHTTPService;
29 import android.net.Uri;
30 import android.os.IBinder;
31 import android.os.IHwBinder;
32 import android.os.PersistableBundle;
33 
34 import com.android.internal.util.Preconditions;
35 
36 import java.io.FileDescriptor;
37 import java.io.IOException;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.UUID;
47 
48 /**
49  * MediaExtractor facilitates extraction of demuxed, typically encoded,  media data
50  * from a data source.
51  * <p>It is generally used like this:
52  * <pre>
53  * MediaExtractor extractor = new MediaExtractor();
54  * extractor.setDataSource(...);
55  * int numTracks = extractor.getTrackCount();
56  * for (int i = 0; i &lt; numTracks; ++i) {
57  *   MediaFormat format = extractor.getTrackFormat(i);
58  *   String mime = format.getString(MediaFormat.KEY_MIME);
59  *   if (weAreInterestedInThisTrack) {
60  *     extractor.selectTrack(i);
61  *   }
62  * }
63  * ByteBuffer inputBuffer = ByteBuffer.allocate(...)
64  * while (extractor.readSampleData(inputBuffer, ...) &gt;= 0) {
65  *   int trackIndex = extractor.getSampleTrackIndex();
66  *   long presentationTimeUs = extractor.getSampleTime();
67  *   ...
68  *   extractor.advance();
69  * }
70  *
71  * extractor.release();
72  * extractor = null;
73  * </pre>
74  *
75  * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
76  * when used with network-based content.
77  */
78 final public class MediaExtractor {
MediaExtractor()79     public MediaExtractor() {
80         native_setup();
81     }
82 
83     /**
84      * Sets the data source (MediaDataSource) to use.
85      *
86      * @param dataSource the MediaDataSource for the media you want to extract from
87      *
88      * @throws IllegalArgumentException if dataSource is invalid.
89      */
setDataSource(@onNull MediaDataSource dataSource)90     public native final void setDataSource(@NonNull MediaDataSource dataSource)
91         throws IOException;
92 
93     /**
94      * Sets the data source as a content Uri.
95      *
96      * @param context the Context to use when resolving the Uri
97      * @param uri the Content URI of the data you want to extract from.
98      *
99      * <p>When <code>uri</code> refers to a network file the
100      * {@link android.Manifest.permission#INTERNET} permission is required.
101      *
102      * @param headers the headers to be sent together with the request for the data.
103      *        This can be {@code null} if no specific headers are to be sent with the
104      *        request.
105      */
setDataSource( @onNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)106     public final void setDataSource(
107             @NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)
108         throws IOException {
109         String scheme = uri.getScheme();
110         if (scheme == null || scheme.equals("file")) {
111             setDataSource(uri.getPath());
112             return;
113         }
114 
115         AssetFileDescriptor fd = null;
116         try {
117             ContentResolver resolver = context.getContentResolver();
118             fd = resolver.openAssetFileDescriptor(uri, "r");
119             if (fd == null) {
120                 return;
121             }
122             // Note: using getDeclaredLength so that our behavior is the same
123             // as previous versions when the content provider is returning
124             // a full file.
125             if (fd.getDeclaredLength() < 0) {
126                 setDataSource(fd.getFileDescriptor());
127             } else {
128                 setDataSource(
129                         fd.getFileDescriptor(),
130                         fd.getStartOffset(),
131                         fd.getDeclaredLength());
132             }
133             return;
134         } catch (SecurityException ex) {
135         } catch (IOException ex) {
136         } finally {
137             if (fd != null) {
138                 fd.close();
139             }
140         }
141 
142         setDataSource(uri.toString(), headers);
143     }
144 
145     /**
146      * Sets the data source (file-path or http URL) to use.
147      *
148      * @param path the path of the file, or the http URL
149      *
150      * <p>When <code>path</code> refers to a network file the
151      * {@link android.Manifest.permission#INTERNET} permission is required.
152      *
153      * @param headers the headers associated with the http request for the stream you want to play.
154      *        This can be {@code null} if no specific headers are to be sent with the
155      *        request.
156      */
setDataSource(@onNull String path, @Nullable Map<String, String> headers)157     public final void setDataSource(@NonNull String path, @Nullable Map<String, String> headers)
158         throws IOException {
159         String[] keys = null;
160         String[] values = null;
161 
162         if (headers != null) {
163             keys = new String[headers.size()];
164             values = new String[headers.size()];
165 
166             int i = 0;
167             for (Map.Entry<String, String> entry: headers.entrySet()) {
168                 keys[i] = entry.getKey();
169                 values[i] = entry.getValue();
170                 ++i;
171             }
172         }
173 
174         nativeSetDataSource(
175                 MediaHTTPService.createHttpServiceBinderIfNecessary(path),
176                 path,
177                 keys,
178                 values);
179     }
180 
nativeSetDataSource( @onNull IBinder httpServiceBinder, @NonNull String path, @Nullable String[] keys, @Nullable String[] values)181     private native final void nativeSetDataSource(
182             @NonNull IBinder httpServiceBinder,
183             @NonNull String path,
184             @Nullable String[] keys,
185             @Nullable String[] values) throws IOException;
186 
187     /**
188      * Sets the data source (file-path or http URL) to use.
189      *
190      * @param path the path of the file, or the http URL of the stream
191      *
192      * <p>When <code>path</code> refers to a local file, the file may actually be opened by a
193      * process other than the calling application.  This implies that the pathname
194      * should be an absolute path (as any other process runs with unspecified current working
195      * directory), and that the pathname should reference a world-readable file.
196      * As an alternative, the application could first open the file for reading,
197      * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
198      *
199      * <p>When <code>path</code> refers to a network file the
200      * {@link android.Manifest.permission#INTERNET} permission is required.
201      */
setDataSource(@onNull String path)202     public final void setDataSource(@NonNull String path) throws IOException {
203         nativeSetDataSource(
204                 MediaHTTPService.createHttpServiceBinderIfNecessary(path),
205                 path,
206                 null,
207                 null);
208     }
209 
210     /**
211      * Sets the data source (AssetFileDescriptor) to use. It is the caller's
212      * responsibility to close the file descriptor. It is safe to do so as soon
213      * as this call returns.
214      *
215      * @param afd the AssetFileDescriptor for the file you want to extract from.
216      */
setDataSource(@onNull AssetFileDescriptor afd)217     public final void setDataSource(@NonNull AssetFileDescriptor afd)
218             throws IOException, IllegalArgumentException, IllegalStateException {
219         Preconditions.checkNotNull(afd);
220         // Note: using getDeclaredLength so that our behavior is the same
221         // as previous versions when the content provider is returning
222         // a full file.
223         if (afd.getDeclaredLength() < 0) {
224             setDataSource(afd.getFileDescriptor());
225         } else {
226             setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
227         }
228     }
229 
230     /**
231      * Sets the data source (FileDescriptor) to use. It is the caller's responsibility
232      * to close the file descriptor. It is safe to do so as soon as this call returns.
233      *
234      * @param fd the FileDescriptor for the file you want to extract from.
235      */
setDataSource(@onNull FileDescriptor fd)236     public final void setDataSource(@NonNull FileDescriptor fd) throws IOException {
237         setDataSource(fd, 0, 0x7ffffffffffffffL);
238     }
239 
240     /**
241      * Sets the data source (FileDescriptor) to use.  The FileDescriptor must be
242      * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
243      * to close the file descriptor. It is safe to do so as soon as this call returns.
244      *
245      * @param fd the FileDescriptor for the file you want to extract from.
246      * @param offset the offset into the file where the data to be extracted starts, in bytes
247      * @param length the length in bytes of the data to be extracted
248      */
setDataSource( @onNull FileDescriptor fd, long offset, long length)249     public native final void setDataSource(
250             @NonNull FileDescriptor fd, long offset, long length) throws IOException;
251 
252     /**
253      * Sets the MediaCas instance to use. This should be called after a
254      * successful setDataSource() if at least one track reports mime type
255      * of {@link android.media.MediaFormat#MIMETYPE_AUDIO_SCRAMBLED}
256      * or {@link android.media.MediaFormat#MIMETYPE_VIDEO_SCRAMBLED}.
257      * Stream parsing will not proceed until a valid MediaCas object
258      * is provided.
259      *
260      * @param mediaCas the MediaCas object to use.
261      */
setMediaCas(@onNull MediaCas mediaCas)262     public final void setMediaCas(@NonNull MediaCas mediaCas) {
263         mMediaCas = mediaCas;
264         nativeSetMediaCas(mediaCas.getBinder());
265     }
266 
nativeSetMediaCas(@onNull IHwBinder casBinder)267     private native final void nativeSetMediaCas(@NonNull IHwBinder casBinder);
268 
269     /**
270      * Describes the conditional access system used to scramble a track.
271      */
272     public static final class CasInfo {
273         private final int mSystemId;
274         private final MediaCas.Session mSession;
275         private final byte[] mPrivateData;
276 
CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData)277         CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData) {
278             mSystemId = systemId;
279             mSession = session;
280             mPrivateData = privateData;
281         }
282 
283         /**
284          * Retrieves the system id of the conditional access system.
285          *
286          * @return CA system id of the CAS used to scramble the track.
287          */
getSystemId()288         public int getSystemId() {
289             return mSystemId;
290         }
291 
292         /**
293          * Retrieves the private data in the CA_Descriptor associated with a track.
294          * Some CAS systems may need this to initialize the CAS plugin object. This
295          * private data can only be retrieved before a valid {@link MediaCas} object
296          * is set on the extractor.
297          * <p>
298          * @see MediaExtractor#setMediaCas
299          * <p>
300          * @return a byte array containing the private data. A null return value
301          *         indicates that the private data is unavailable. An empty array,
302          *         on the other hand, indicates that the private data is empty
303          *         (zero in length).
304          */
305         @Nullable
getPrivateData()306         public byte[] getPrivateData() {
307             return mPrivateData;
308         }
309 
310         /**
311          * Retrieves the {@link MediaCas.Session} associated with a track. The
312          * session is needed to initialize a descrambler in order to decode the
313          * scrambled track. The session object can only be retrieved after a valid
314          * {@link MediaCas} object is set on the extractor.
315          * <p>
316          * @see MediaExtractor#setMediaCas
317          * @see MediaDescrambler#setMediaCasSession
318          * <p>
319          * @return a {@link MediaCas.Session} object associated with a track.
320          */
getSession()321         public MediaCas.Session getSession() {
322             return mSession;
323         }
324     }
325 
toByteArray(@onNull byte[] data)326     private ArrayList<Byte> toByteArray(@NonNull byte[] data) {
327         ArrayList<Byte> byteArray = new ArrayList<Byte>(data.length);
328         for (int i = 0; i < data.length; i++) {
329             byteArray.add(i, Byte.valueOf(data[i]));
330         }
331         return byteArray;
332     }
333 
334     /**
335      * Retrieves the information about the conditional access system used to scramble
336      * a track.
337      *
338      * @param index of the track.
339      * @return an {@link CasInfo} object describing the conditional access system.
340      */
getCasInfo(int index)341     public CasInfo getCasInfo(int index) {
342         Map<String, Object> formatMap = getTrackFormatNative(index);
343         if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
344             int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
345             MediaCas.Session session = null;
346             byte[] privateData = null;
347             if (formatMap.containsKey(MediaFormat.KEY_CA_PRIVATE_DATA)) {
348                 ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_PRIVATE_DATA);
349                 buf.rewind();
350                 privateData = new byte[buf.remaining()];
351                 buf.get(privateData);
352             }
353             if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
354                 ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
355                 buf.rewind();
356                 final byte[] sessionId = new byte[buf.remaining()];
357                 buf.get(sessionId);
358                 session = mMediaCas.createFromSessionId(toByteArray(sessionId));
359             }
360             return new CasInfo(systemId, session, privateData);
361         }
362         return null;
363     }
364 
365     @Override
finalize()366     protected void finalize() {
367         native_finalize();
368     }
369 
370     /**
371      * Make sure you call this when you're done to free up any resources
372      * instead of relying on the garbage collector to do this for you at
373      * some point in the future.
374      */
release()375     public native final void release();
376 
377     /**
378      * Count the number of tracks found in the data source.
379      */
getTrackCount()380     public native final int getTrackCount();
381 
382     /**
383      * Extract DRM initialization data if it exists
384      *
385      * @return DRM initialization data in the content, or {@code null}
386      * if no recognizable DRM format is found;
387      * @see DrmInitData
388      */
getDrmInitData()389     public DrmInitData getDrmInitData() {
390         Map<String, Object> formatMap = getFileFormatNative();
391         if (formatMap == null) {
392             return null;
393         }
394         if (formatMap.containsKey("pssh")) {
395             Map<UUID, byte[]> psshMap = getPsshInfo();
396             final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
397                 new HashMap<UUID, DrmInitData.SchemeInitData>();
398             for (Map.Entry<UUID, byte[]> e: psshMap.entrySet()) {
399                 UUID uuid = e.getKey();
400                 byte[] data = e.getValue();
401                 initDataMap.put(uuid, new DrmInitData.SchemeInitData("cenc", data));
402             }
403             return new DrmInitData() {
404                 public SchemeInitData get(UUID schemeUuid) {
405                     return initDataMap.get(schemeUuid);
406                 }
407             };
408         } else {
409             int numTracks = getTrackCount();
410             for (int i = 0; i < numTracks; ++i) {
411                 Map<String, Object> trackFormatMap = getTrackFormatNative(i);
412                 if (!trackFormatMap.containsKey("crypto-key")) {
413                     continue;
414                 }
415                 ByteBuffer buf = (ByteBuffer) trackFormatMap.get("crypto-key");
416                 buf.rewind();
417                 final byte[] data = new byte[buf.remaining()];
418                 buf.get(data);
419                 return new DrmInitData() {
420                     public SchemeInitData get(UUID schemeUuid) {
421                         return new DrmInitData.SchemeInitData("webm", data);
422                     }
423                 };
424             }
425         }
426         return null;
427     }
428 
429     /**
430      * Get the list of available audio presentations for the track.
431      * @param trackIndex index of the track.
432      * @return a list of available audio presentations for a given valid audio track index.
433      * The list will be empty if the source does not contain any audio presentations.
434      */
435     @NonNull
436     public List<AudioPresentation> getAudioPresentations(int trackIndex) {
437         return native_getAudioPresentations(trackIndex);
438     }
439 
440     @NonNull
441     private native List<AudioPresentation> native_getAudioPresentations(int trackIndex);
442 
443     /**
444      * Get the PSSH info if present.
445      * @return a map of uuid-to-bytes, with the uuid specifying
446      * the crypto scheme, and the bytes being the data specific to that scheme.
447      * This can be {@code null} if the source does not contain PSSH info.
448      */
449     @Nullable
450     public Map<UUID, byte[]> getPsshInfo() {
451         Map<UUID, byte[]> psshMap = null;
452         Map<String, Object> formatMap = getFileFormatNative();
453         if (formatMap != null && formatMap.containsKey("pssh")) {
454             ByteBuffer rawpssh = (ByteBuffer) formatMap.get("pssh");
455             rawpssh.order(ByteOrder.nativeOrder());
456             rawpssh.rewind();
457             formatMap.remove("pssh");
458             // parse the flat pssh bytebuffer into something more manageable
459             psshMap = new HashMap<UUID, byte[]>();
460             while (rawpssh.remaining() > 0) {
461                 rawpssh.order(ByteOrder.BIG_ENDIAN);
462                 long msb = rawpssh.getLong();
463                 long lsb = rawpssh.getLong();
464                 UUID uuid = new UUID(msb, lsb);
465                 rawpssh.order(ByteOrder.nativeOrder());
466                 int datalen = rawpssh.getInt();
467                 byte [] psshdata = new byte[datalen];
468                 rawpssh.get(psshdata);
469                 psshMap.put(uuid, psshdata);
470             }
471         }
472         return psshMap;
473     }
474 
475     @NonNull
476     private native Map<String, Object> getFileFormatNative();
477 
478     /**
479      * Get the track format at the specified index.
480      *
481      * More detail on the representation can be found at {@link android.media.MediaCodec}
482      * <p>
483      * The following table summarizes support for format keys across android releases:
484      *
485      * <table style="width: 0%">
486      *  <thead>
487      *   <tr>
488      *    <th rowspan=2>OS Version(s)</th>
489      *    <td colspan=3>{@code MediaFormat} keys used for</th>
490      *   </tr><tr>
491      *    <th>All Tracks</th>
492      *    <th>Audio Tracks</th>
493      *    <th>Video Tracks</th>
494      *   </tr>
495      *  </thead>
496      *  <tbody>
497      *   <tr>
498      *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN}</td>
499      *    <td rowspan=8>{@link MediaFormat#KEY_MIME},<br>
500      *        {@link MediaFormat#KEY_DURATION},<br>
501      *        {@link MediaFormat#KEY_MAX_INPUT_SIZE}</td>
502      *    <td rowspan=5>{@link MediaFormat#KEY_SAMPLE_RATE},<br>
503      *        {@link MediaFormat#KEY_CHANNEL_COUNT},<br>
504      *        {@link MediaFormat#KEY_CHANNEL_MASK},<br>
505      *        gapless playback information<sup>.mp3, .mp4</sup>,<br>
506      *        {@link MediaFormat#KEY_IS_ADTS}<sup>AAC if streaming</sup>,<br>
507      *        codec-specific data<sup>AAC, Vorbis</sup></td>
508      *    <td rowspan=2>{@link MediaFormat#KEY_WIDTH},<br>
509      *        {@link MediaFormat#KEY_HEIGHT},<br>
510      *        codec-specific data<sup>AVC, MPEG4</sup></td>
511      *   </tr><tr>
512      *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}</td>
513      *   </tr><tr>
514      *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td>
515      *    <td rowspan=3>as above, plus<br>
516      *        Pixel aspect ratio information<sup>AVC, *</sup></td>
517      *   </tr><tr>
518      *    <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td>
519      *   </tr><tr>
520      *    <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td>
521      *   </tr><tr>
522      *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td>
523      *    <td rowspan=2>as above, plus<br>
524      *        {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup>,<br>
525      *        codec-specific data<sup>Opus</sup></td>
526      *    <td rowspan=2>as above, plus<br>
527      *        {@link MediaFormat#KEY_ROTATION}<sup>.mp4</sup>,<br>
528      *        {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br>
529      *        codec-specific data<sup>HEVC</sup></td>
530      *   </tr><tr>
531      *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td>
532      *   </tr><tr>
533      *    <td>{@link android.os.Build.VERSION_CODES#M}</td>
534      *    <td>as above, plus<br>
535      *        gapless playback information<sup>Opus</sup></td>
536      *    <td>as above, plus<br>
537      *        {@link MediaFormat#KEY_FRAME_RATE} (integer)</td>
538      *   </tr><tr>
539      *    <td>{@link android.os.Build.VERSION_CODES#N}</td>
540      *    <td>as above, plus<br>
541      *        {@link MediaFormat#KEY_TRACK_ID},<br>
542      *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>#, .mp4</sup>,<br> -->
543      *        {@link MediaFormat#KEY_BIT_RATE}<sup>#, .mp4</sup></td>
544      *    <td>as above, plus<br>
545      *        {@link MediaFormat#KEY_PCM_ENCODING},<br>
546      *        {@link MediaFormat#KEY_PROFILE}<sup>AAC</sup></td>
547      *    <td>as above, plus<br>
548      *        {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br>
549      *        {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br>
550      *        {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br>
551      *        {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br>
552      *        {@link MediaFormat#KEY_PROFILE}<sup>MPEG2, H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
553      *        {@link MediaFormat#KEY_LEVEL}<sup>H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
554      *        codec-specific data<sup>VP9</sup></td>
555      *   </tr>
556      *   <tr>
557      *    <td colspan=4>
558      *     <p class=note><strong>Notes:</strong><br>
559      *      #: container-specified value only.<br>
560      *      .mp4, .webm&hellip;: for listed containers<br>
561      *      MPEG4, AAC&hellip;: for listed codecs
562      *    </td>
563      *   </tr><tr>
564      *    <td colspan=4>
565      *     <p class=note>Note that that level information contained in the container many times
566      *     does not match the level of the actual bitstream. You may want to clear the level using
567      *     {@code MediaFormat.setString(KEY_LEVEL, null)} before using the track format to find a
568      *     decoder that can play back a particular track.
569      *    </td>
570      *   </tr><tr>
571      *    <td colspan=4>
572      *     <p class=note><strong>*Pixel (sample) aspect ratio</strong> is returned in the following
573      *     keys. The display width can be calculated for example as:
574      *     <p align=center>
575      *     display-width = display-height * crop-width / crop-height * sar-width / sar-height
576      *    </td>
577      *   </tr><tr>
578      *    <th>Format Key</th><th>Value Type</th><th colspan=2>Description</th>
579      *   </tr><tr>
580      *    <td>{@code "sar-width"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio width</td>
581      *   </tr><tr>
582      *    <td>{@code "sar-height"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio height</td>
583      *   </tr>
584      *  </tbody>
585      * </table>
586      *
587      */
588     @NonNull
589     public MediaFormat getTrackFormat(int index) {
590         return new MediaFormat(getTrackFormatNative(index));
591     }
592 
593     @NonNull
594     private native Map<String, Object> getTrackFormatNative(int index);
595 
596     /**
597      * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
598      * {@link #getSampleTime} only retrieve information for the subset of tracks
599      * selected.
600      * Selecting the same track multiple times has no effect, the track is
601      * only selected once.
602      */
603     public native void selectTrack(int index);
604 
605     /**
606      * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
607      * {@link #getSampleTime} only retrieve information for the subset of tracks
608      * selected.
609      */
610     public native void unselectTrack(int index);
611 
612     /**
613      * If possible, seek to a sync sample at or before the specified time
614      */
615     public static final int SEEK_TO_PREVIOUS_SYNC       = 0;
616     /**
617      * If possible, seek to a sync sample at or after the specified time
618      */
619     public static final int SEEK_TO_NEXT_SYNC           = 1;
620     /**
621      * If possible, seek to the sync sample closest to the specified time
622      */
623     public static final int SEEK_TO_CLOSEST_SYNC        = 2;
624 
625     /** @hide */
626     @IntDef({
627         SEEK_TO_PREVIOUS_SYNC,
628         SEEK_TO_NEXT_SYNC,
629         SEEK_TO_CLOSEST_SYNC,
630     })
631     @Retention(RetentionPolicy.SOURCE)
632     public @interface SeekMode {}
633 
634     /**
635      * All selected tracks seek near the requested time according to the
636      * specified mode.
637      */
638     public native void seekTo(long timeUs, @SeekMode int mode);
639 
640     /**
641      * Advance to the next sample. Returns false if no more sample data
642      * is available (end of stream).
643      *
644      * When extracting a local file, the behaviors of {@link #advance} and
645      * {@link #readSampleData} are undefined in presence of concurrent
646      * writes to the same local file; more specifically, end of stream
647      * could be signalled earlier than expected.
648      */
649     public native boolean advance();
650 
651     /**
652      * Retrieve the current encoded sample and store it in the byte buffer
653      * starting at the given offset.
654      * <p>
655      * <b>Note:</b>As of API 21, on success the position and limit of
656      * {@code byteBuf} is updated to point to the data just read.
657      * @param byteBuf the destination byte buffer
658      * @return the sample size (or -1 if no more samples are available).
659      */
660     public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);
661 
662     /**
663      * Returns the track index the current sample originates from (or -1
664      * if no more samples are available)
665      */
666     public native int getSampleTrackIndex();
667 
668     /**
669      * Returns the current sample's presentation time in microseconds.
670      * or -1 if no more samples are available.
671      */
672     public native long getSampleTime();
673 
674     /**
675      * @return size of the current sample in bytes or -1 if no more
676      * samples are available.
677      */
678     public native long getSampleSize();
679 
680     // Keep these in sync with their equivalents in NuMediaExtractor.h
681     /**
682      * The sample is a sync sample (or in {@link MediaCodec}'s terminology
683      * it is a key frame.)
684      *
685      * @see MediaCodec#BUFFER_FLAG_KEY_FRAME
686      */
687     public static final int SAMPLE_FLAG_SYNC      = 1;
688 
689     /**
690      * The sample is (at least partially) encrypted, see also the documentation
691      * for {@link android.media.MediaCodec#queueSecureInputBuffer}
692      */
693     public static final int SAMPLE_FLAG_ENCRYPTED = 2;
694 
695     /**
696      * This indicates that the buffer only contains part of a frame,
697      * and the decoder should batch the data until a buffer without
698      * this flag appears before decoding the frame.
699      *
700      * @see MediaCodec#BUFFER_FLAG_PARTIAL_FRAME
701      */
702     public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4;
703 
704     /** @hide */
705     @IntDef(
706         flag = true,
707         value = {
708             SAMPLE_FLAG_SYNC,
709             SAMPLE_FLAG_ENCRYPTED,
710             SAMPLE_FLAG_PARTIAL_FRAME,
711     })
712     @Retention(RetentionPolicy.SOURCE)
713     public @interface SampleFlag {}
714 
715     /**
716      * Returns the current sample's flags.
717      */
718     @SampleFlag
719     public native int getSampleFlags();
720 
721     /**
722      * If the sample flags indicate that the current sample is at least
723      * partially encrypted, this call returns relevant information about
724      * the structure of the sample data required for decryption.
725      * @param info The android.media.MediaCodec.CryptoInfo structure
726      *             to be filled in.
727      * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED}
728      */
729     public native boolean getSampleCryptoInfo(@NonNull MediaCodec.CryptoInfo info);
730 
731     /**
732      * Returns an estimate of how much data is presently cached in memory
733      * expressed in microseconds. Returns -1 if that information is unavailable
734      * or not applicable (no cache).
735      */
736     public native long getCachedDuration();
737 
738     /**
739      * Returns true iff we are caching data and the cache has reached the
740      * end of the data stream (for now, a future seek may of course restart
741      * the fetching of data).
742      * This API only returns a meaningful result if {@link #getCachedDuration}
743      * indicates the presence of a cache, i.e. does NOT return -1.
744      */
745     public native boolean hasCacheReachedEndOfStream();
746 
747     /**
748      *  Return Metrics data about the current media container.
749      *
750      * @return a {@link PersistableBundle} containing the set of attributes and values
751      * available for the media container being handled by this instance
752      * of MediaExtractor.
753      * The attributes are descibed in {@link MetricsConstants}.
754      *
755      *  Additional vendor-specific fields may also be present in
756      *  the return value.
757      */
758 
759     public PersistableBundle getMetrics() {
760         PersistableBundle bundle = native_getMetrics();
761         return bundle;
762     }
763 
764     private native PersistableBundle native_getMetrics();
765 
766     private static native final void native_init();
767     private native final void native_setup();
768     private native final void native_finalize();
769 
770     static {
771         System.loadLibrary("media_jni");
772         native_init();
773     }
774 
775     private MediaCas mMediaCas;
776 
777     private long mNativeContext;
778 
779     public final static class MetricsConstants
780     {
781         private MetricsConstants() {}
782 
783         /**
784          * Key to extract the container format
785          * from the {@link MediaExtractor#getMetrics} return value.
786          * The value is a String.
787          */
788         public static final String FORMAT = "android.media.mediaextractor.fmt";
789 
790         /**
791          * Key to extract the container MIME type
792          * from the {@link MediaExtractor#getMetrics} return value.
793          * The value is a String.
794          */
795         public static final String MIME_TYPE = "android.media.mediaextractor.mime";
796 
797         /**
798          * Key to extract the number of tracks in the container
799          * from the {@link MediaExtractor#getMetrics} return value.
800          * The value is an integer.
801          */
802         public static final String TRACKS = "android.media.mediaextractor.ntrk";
803 
804     }
805 
806 }
807