2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.media;
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;
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;
41 import java.util.UUID;
44 * MediaExtractor facilitates extraction of demuxed, typically encoded, media data
46 * <p>It is generally used like this:
48 * MediaExtractor extractor = new MediaExtractor();
49 * extractor.setDataSource(...);
50 * int numTracks = extractor.getTrackCount();
51 * for (int i = 0; i < numTracks; ++i) {
52 * MediaFormat format = extractor.getTrackFormat(i);
53 * String mime = format.getString(MediaFormat.KEY_MIME);
54 * if (weAreInterestedInThisTrack) {
55 * extractor.selectTrack(i);
58 * ByteBuffer inputBuffer = ByteBuffer.allocate(...)
59 * while (extractor.readSampleData(inputBuffer, ...) >= 0) {
60 * int trackIndex = extractor.getSampleTrackIndex();
61 * long presentationTimeUs = extractor.getSampleTime();
63 * extractor.advance();
66 * extractor.release();
70 final public class MediaExtractor {
71 public MediaExtractor() {
76 * Sets the data source (MediaDataSource) to use.
78 * @param dataSource the MediaDataSource for the media you want to extract from
80 * @throws IllegalArgumentException if dataSource is invalid.
82 public native final void setDataSource(@NonNull MediaDataSource dataSource)
86 * Sets the data source as a content Uri.
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
94 public final void setDataSource(
95 @NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers)
97 String scheme = uri.getScheme();
98 if (scheme == null || scheme.equals("file")) {
99 setDataSource(uri.getPath());
103 AssetFileDescriptor fd = null;
105 ContentResolver resolver = context.getContentResolver();
106 fd = resolver.openAssetFileDescriptor(uri, "r");
110 // Note: using getDeclaredLength so that our behavior is the same
111 // as previous versions when the content provider is returning
113 if (fd.getDeclaredLength() < 0) {
114 setDataSource(fd.getFileDescriptor());
117 fd.getFileDescriptor(),
119 fd.getDeclaredLength());
122 } catch (SecurityException ex) {
123 } catch (IOException ex) {
130 setDataSource(uri.toString(), headers);
134 * Sets the data source (file-path or http URL) to use.
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
141 public final void setDataSource(@NonNull String path, @Nullable Map<String, String> headers)
143 String[] keys = null;
144 String[] values = null;
146 if (headers != null) {
147 keys = new String[headers.size()];
148 values = new String[headers.size()];
151 for (Map.Entry<String, String> entry: headers.entrySet()) {
152 keys[i] = entry.getKey();
153 values[i] = entry.getValue();
159 MediaHTTPService.createHttpServiceBinderIfNecessary(path),
165 private native final void nativeSetDataSource(
166 @NonNull IBinder httpServiceBinder,
167 @NonNull String path,
168 @Nullable String[] keys,
169 @Nullable String[] values) throws IOException;
172 * Sets the data source (file-path or http URL) to use.
174 * @param path the path of the file, or the http URL of the stream
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)}.
183 public final void setDataSource(@NonNull String path) throws IOException {
185 MediaHTTPService.createHttpServiceBinderIfNecessary(path),
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.
195 * @param fd the FileDescriptor for the file you want to extract from.
197 public final void setDataSource(@NonNull FileDescriptor fd) throws IOException {
198 setDataSource(fd, 0, 0x7ffffffffffffffL);
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.
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
210 public native final void setDataSource(
211 @NonNull FileDescriptor fd, long offset, long length) throws IOException;
214 protected void finalize() {
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.
223 public native final void release();
226 * Count the number of tracks found in the data source.
228 public native final int getTrackCount();
231 * Extract DRM initialization data if it exists
233 * @return DRM initialization data in the content, or {@code null}
234 * if no recognizable DRM format is found;
237 public DrmInitData getDrmInitData() {
238 Map<String, Object> formatMap = getFileFormatNative();
239 if (formatMap == null) {
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));
251 return new DrmInitData() {
252 public SchemeInitData get(UUID schemeUuid) {
253 return initDataMap.get(schemeUuid);
256 } else if (formatMap.containsKey("crypto-key")) {
257 ByteBuffer buf = (ByteBuffer) formatMap.get("crypto-key");
259 final byte[] data = new byte[buf.remaining()];
261 return new DrmInitData() {
262 public SchemeInitData get(UUID schemeUuid) {
263 return new DrmInitData.SchemeInitData("webm", data);
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.
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());
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);
303 private native Map<String, Object> getFileFormatNative();
306 * Get the track format at the specified index.
307 * More detail on the representation can be found at {@link android.media.MediaCodec}
310 public MediaFormat getTrackFormat(int index) {
311 return new MediaFormat(getTrackFormatNative(index));
315 private native Map<String, Object> getTrackFormatNative(int index);
318 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
319 * {@link #getSampleTime} only retrieve information for the subset of tracks
321 * Selecting the same track multiple times has no effect, the track is
322 * only selected once.
324 public native void selectTrack(int index);
327 * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
328 * {@link #getSampleTime} only retrieve information for the subset of tracks
331 public native void unselectTrack(int index);
334 * If possible, seek to a sync sample at or before the specified time
336 public static final int SEEK_TO_PREVIOUS_SYNC = 0;
338 * If possible, seek to a sync sample at or after the specified time
340 public static final int SEEK_TO_NEXT_SYNC = 1;
342 * If possible, seek to the sync sample closest to the specified time
344 public static final int SEEK_TO_CLOSEST_SYNC = 2;
348 SEEK_TO_PREVIOUS_SYNC,
350 SEEK_TO_CLOSEST_SYNC,
352 @Retention(RetentionPolicy.SOURCE)
353 public @interface SeekMode {}
356 * All selected tracks seek near the requested time according to the
359 public native void seekTo(long timeUs, @SeekMode int mode);
362 * Advance to the next sample. Returns false if no more sample data
363 * is available (end of stream).
365 public native boolean advance();
368 * Retrieve the current encoded sample and store it in the byte buffer
369 * starting at the given offset.
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).
376 public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);
379 * Returns the track index the current sample originates from (or -1
380 * if no more samples are available)
382 public native int getSampleTrackIndex();
385 * Returns the current sample's presentation time in microseconds.
386 * or -1 if no more samples are available.
388 public native long getSampleTime();
390 // Keep these in sync with their equivalents in NuMediaExtractor.h
392 * The sample is a sync sample (or in {@link MediaCodec}'s terminology
393 * it is a key frame.)
395 * @see MediaCodec#BUFFER_FLAG_KEY_FRAME
397 public static final int SAMPLE_FLAG_SYNC = 1;
400 * The sample is (at least partially) encrypted, see also the documentation
401 * for {@link android.media.MediaCodec#queueSecureInputBuffer}
403 public static final int SAMPLE_FLAG_ENCRYPTED = 2;
410 SAMPLE_FLAG_ENCRYPTED,
412 @Retention(RetentionPolicy.SOURCE)
413 public @interface SampleFlag {}
416 * Returns the current sample's flags.
419 public native int getSampleFlags();
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
427 * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED}
429 public native boolean getSampleCryptoInfo(@NonNull MediaCodec.CryptoInfo info);
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).
436 public native long getCachedDuration();
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.
445 public native boolean hasCacheReachedEndOfStream();
447 private static native final void native_init();
448 private native final void native_setup();
449 private native final void native_finalize();
452 System.loadLibrary("media_jni");
456 private long mNativeContext;