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