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 java.io.FileDescriptor;
32import java.io.IOException;
33import java.lang.annotation.Retention;
34import java.lang.annotation.RetentionPolicy;
35import java.nio.ByteBuffer;
36import java.nio.ByteOrder;
37import java.util.HashMap;
38import java.util.Map;
39import java.util.UUID;
40
41/**
42 * MediaExtractor facilitates extraction of demuxed, typically encoded,  media data
43 * from a data source.
44 * <p>It is generally used like this:
45 * <pre>
46 * MediaExtractor extractor = new MediaExtractor();
47 * extractor.setDataSource(...);
48 * int numTracks = extractor.getTrackCount();
49 * for (int i = 0; i &lt; numTracks; ++i) {
50 *   MediaFormat format = extractor.getTrackFormat(i);
51 *   String mime = format.getString(MediaFormat.KEY_MIME);
52 *   if (weAreInterestedInThisTrack) {
53 *     extractor.selectTrack(i);
54 *   }
55 * }
56 * ByteBuffer inputBuffer = ByteBuffer.allocate(...)
57 * while (extractor.readSampleData(inputBuffer, ...) &gt;= 0) {
58 *   int trackIndex = extractor.getSampleTrackIndex();
59 *   long presentationTimeUs = extractor.getSampleTime();
60 *   ...
61 *   extractor.advance();
62 * }
63 *
64 * extractor.release();
65 * extractor = null;
66 * </pre>
67 */
68final public class MediaExtractor {
69    public MediaExtractor() {
70        native_setup();
71    }
72
73    /**
74     * Sets the data source (MediaDataSource) to use.
75     *
76     * @param dataSource the MediaDataSource for the media you want to extract from
77     *
78     * @throws IllegalArgumentException if dataSource is invalid.
79     */
80    public native final void setDataSource(@NonNull MediaDataSource dataSource)
81        throws IOException;
82
83    /**
84     * Sets the data source as a content Uri.
85     *
86     * @param context the Context to use when resolving the Uri
87     * @param uri the Content URI of the data you want to extract from.
88     * @param headers the headers to be sent together with the request for the data.
89     *        This can be {@code null} if no specific headers are to be sent with the
90     *        request.
91     */
92    public final void setDataSource(
93            @NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)
94        throws IOException {
95        String scheme = uri.getScheme();
96        if (scheme == null || scheme.equals("file")) {
97            setDataSource(uri.getPath());
98            return;
99        }
100
101        AssetFileDescriptor fd = null;
102        try {
103            ContentResolver resolver = context.getContentResolver();
104            fd = resolver.openAssetFileDescriptor(uri, "r");
105            if (fd == null) {
106                return;
107            }
108            // Note: using getDeclaredLength so that our behavior is the same
109            // as previous versions when the content provider is returning
110            // a full file.
111            if (fd.getDeclaredLength() < 0) {
112                setDataSource(fd.getFileDescriptor());
113            } else {
114                setDataSource(
115                        fd.getFileDescriptor(),
116                        fd.getStartOffset(),
117                        fd.getDeclaredLength());
118            }
119            return;
120        } catch (SecurityException ex) {
121        } catch (IOException ex) {
122        } finally {
123            if (fd != null) {
124                fd.close();
125            }
126        }
127
128        setDataSource(uri.toString(), headers);
129    }
130
131    /**
132     * Sets the data source (file-path or http URL) to use.
133     *
134     * @param path the path of the file, or the http URL
135     * @param headers the headers associated with the http request for the stream you want to play.
136     *        This can be {@code null} if no specific headers are to be sent with the
137     *        request.
138     */
139    public final void setDataSource(@NonNull String path, @Nullable Map<String, String> headers)
140        throws IOException {
141        String[] keys = null;
142        String[] values = null;
143
144        if (headers != null) {
145            keys = new String[headers.size()];
146            values = new String[headers.size()];
147
148            int i = 0;
149            for (Map.Entry<String, String> entry: headers.entrySet()) {
150                keys[i] = entry.getKey();
151                values[i] = entry.getValue();
152                ++i;
153            }
154        }
155
156        nativeSetDataSource(
157                MediaHTTPService.createHttpServiceBinderIfNecessary(path),
158                path,
159                keys,
160                values);
161    }
162
163    private native final void nativeSetDataSource(
164            @NonNull IBinder httpServiceBinder,
165            @NonNull String path,
166            @Nullable String[] keys,
167            @Nullable String[] values) throws IOException;
168
169    /**
170     * Sets the data source (file-path or http URL) to use.
171     *
172     * @param path the path of the file, or the http URL of the stream
173     *
174     * <p>When <code>path</code> refers to a local file, the file may actually be opened by a
175     * process other than the calling application.  This implies that the pathname
176     * should be an absolute path (as any other process runs with unspecified current working
177     * directory), and that the pathname should reference a world-readable file.
178     * As an alternative, the application could first open the file for reading,
179     * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
180     */
181    public final void setDataSource(@NonNull String path) throws IOException {
182        nativeSetDataSource(
183                MediaHTTPService.createHttpServiceBinderIfNecessary(path),
184                path,
185                null,
186                null);
187    }
188
189    /**
190     * Sets the data source (FileDescriptor) to use. It is the caller's responsibility
191     * to close the file descriptor. It is safe to do so as soon as this call returns.
192     *
193     * @param fd the FileDescriptor for the file you want to extract from.
194     */
195    public final void setDataSource(@NonNull FileDescriptor fd) throws IOException {
196        setDataSource(fd, 0, 0x7ffffffffffffffL);
197    }
198
199    /**
200     * Sets the data source (FileDescriptor) to use.  The FileDescriptor must be
201     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
202     * to close the file descriptor. It is safe to do so as soon as this call returns.
203     *
204     * @param fd the FileDescriptor for the file you want to extract from.
205     * @param offset the offset into the file where the data to be extracted starts, in bytes
206     * @param length the length in bytes of the data to be extracted
207     */
208    public native final void setDataSource(
209            @NonNull FileDescriptor fd, long offset, long length) throws IOException;
210
211    @Override
212    protected void finalize() {
213        native_finalize();
214    }
215
216    /**
217     * Make sure you call this when you're done to free up any resources
218     * instead of relying on the garbage collector to do this for you at
219     * some point in the future.
220     */
221    public native final void release();
222
223    /**
224     * Count the number of tracks found in the data source.
225     */
226    public native final int getTrackCount();
227
228    /**
229     * Get the PSSH info if present.
230     * @return a map of uuid-to-bytes, with the uuid specifying
231     * the crypto scheme, and the bytes being the data specific to that scheme.
232     * This can be {@code null} if the source does not contain PSSH info.
233     */
234    @Nullable
235    public Map<UUID, byte[]> getPsshInfo() {
236        Map<UUID, byte[]> psshMap = null;
237        Map<String, Object> formatMap = getFileFormatNative();
238        if (formatMap != null && formatMap.containsKey("pssh")) {
239            ByteBuffer rawpssh = (ByteBuffer) formatMap.get("pssh");
240            rawpssh.order(ByteOrder.nativeOrder());
241            rawpssh.rewind();
242            formatMap.remove("pssh");
243            // parse the flat pssh bytebuffer into something more manageable
244            psshMap = new HashMap<UUID, byte[]>();
245            while (rawpssh.remaining() > 0) {
246                rawpssh.order(ByteOrder.BIG_ENDIAN);
247                long msb = rawpssh.getLong();
248                long lsb = rawpssh.getLong();
249                UUID uuid = new UUID(msb, lsb);
250                rawpssh.order(ByteOrder.nativeOrder());
251                int datalen = rawpssh.getInt();
252                byte [] psshdata = new byte[datalen];
253                rawpssh.get(psshdata);
254                psshMap.put(uuid, psshdata);
255            }
256        }
257        return psshMap;
258    }
259
260    @NonNull
261    private native Map<String, Object> getFileFormatNative();
262
263    /**
264     * Get the track format at the specified index.
265     * More detail on the representation can be found at {@link android.media.MediaCodec}
266     */
267    @NonNull
268    public MediaFormat getTrackFormat(int index) {
269        return new MediaFormat(getTrackFormatNative(index));
270    }
271
272    @NonNull
273    private native Map<String, Object> getTrackFormatNative(int index);
274
275    /**
276     * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
277     * {@link #getSampleTime} only retrieve information for the subset of tracks
278     * selected.
279     * Selecting the same track multiple times has no effect, the track is
280     * only selected once.
281     */
282    public native void selectTrack(int index);
283
284    /**
285     * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
286     * {@link #getSampleTime} only retrieve information for the subset of tracks
287     * selected.
288     */
289    public native void unselectTrack(int index);
290
291    /**
292     * If possible, seek to a sync sample at or before the specified time
293     */
294    public static final int SEEK_TO_PREVIOUS_SYNC       = 0;
295    /**
296     * If possible, seek to a sync sample at or after the specified time
297     */
298    public static final int SEEK_TO_NEXT_SYNC           = 1;
299    /**
300     * If possible, seek to the sync sample closest to the specified time
301     */
302    public static final int SEEK_TO_CLOSEST_SYNC        = 2;
303
304    /** @hide */
305    @IntDef({
306        SEEK_TO_PREVIOUS_SYNC,
307        SEEK_TO_NEXT_SYNC,
308        SEEK_TO_CLOSEST_SYNC,
309    })
310    @Retention(RetentionPolicy.SOURCE)
311    public @interface SeekMode {}
312
313    /**
314     * All selected tracks seek near the requested time according to the
315     * specified mode.
316     */
317    public native void seekTo(long timeUs, @SeekMode int mode);
318
319    /**
320     * Advance to the next sample. Returns false if no more sample data
321     * is available (end of stream).
322     */
323    public native boolean advance();
324
325    /**
326     * Retrieve the current encoded sample and store it in the byte buffer
327     * starting at the given offset.
328     * <p>
329     * <b>Note:</b>As of API 21, on success the position and limit of
330     * {@code byteBuf} is updated to point to the data just read.
331     * @param byteBuf the destination byte buffer
332     * @return the sample size (or -1 if no more samples are available).
333     */
334    public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);
335
336    /**
337     * Returns the track index the current sample originates from (or -1
338     * if no more samples are available)
339     */
340    public native int getSampleTrackIndex();
341
342    /**
343     * Returns the current sample's presentation time in microseconds.
344     * or -1 if no more samples are available.
345     */
346    public native long getSampleTime();
347
348    // Keep these in sync with their equivalents in NuMediaExtractor.h
349    /**
350     * The sample is a sync sample (or in {@link MediaCodec}'s terminology
351     * it is a key frame.)
352     *
353     * @see MediaCodec#BUFFER_FLAG_KEY_FRAME
354     */
355    public static final int SAMPLE_FLAG_SYNC      = 1;
356
357    /**
358     * The sample is (at least partially) encrypted, see also the documentation
359     * for {@link android.media.MediaCodec#queueSecureInputBuffer}
360     */
361    public static final int SAMPLE_FLAG_ENCRYPTED = 2;
362
363    /** @hide */
364    @IntDef(
365        flag = true,
366        value = {
367            SAMPLE_FLAG_SYNC,
368            SAMPLE_FLAG_ENCRYPTED,
369    })
370    @Retention(RetentionPolicy.SOURCE)
371    public @interface SampleFlag {}
372
373    /**
374     * Returns the current sample's flags.
375     */
376    @SampleFlag
377    public native int getSampleFlags();
378
379    /**
380     * If the sample flags indicate that the current sample is at least
381     * partially encrypted, this call returns relevant information about
382     * the structure of the sample data required for decryption.
383     * @param info The android.media.MediaCodec.CryptoInfo structure
384     *             to be filled in.
385     * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED}
386     */
387    public native boolean getSampleCryptoInfo(@NonNull MediaCodec.CryptoInfo info);
388
389    /**
390     * Returns an estimate of how much data is presently cached in memory
391     * expressed in microseconds. Returns -1 if that information is unavailable
392     * or not applicable (no cache).
393     */
394    public native long getCachedDuration();
395
396    /**
397     * Returns true iff we are caching data and the cache has reached the
398     * end of the data stream (for now, a future seek may of course restart
399     * the fetching of data).
400     * This API only returns a meaningful result if {@link #getCachedDuration}
401     * indicates the presence of a cache, i.e. does NOT return -1.
402     */
403    public native boolean hasCacheReachedEndOfStream();
404
405    private static native final void native_init();
406    private native final void native_setup();
407    private native final void native_finalize();
408
409    static {
410        System.loadLibrary("media_jni");
411        native_init();
412    }
413
414    private long mNativeContext;
415}
416