MediaExtractor.java revision db56549ff24df1f5fc3ff7a816274a69e3fe4c3e
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 &lt; 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, ...) &gt;= 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&hellip;: for listed containers<br>
417     *      MPEG4, AAC&hellip;: 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