OSDN Git Service

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