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