/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaHTTPService; import android.net.Uri; import android.os.IBinder; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * MediaExtractor facilitates extraction of demuxed, typically encoded, media data * from a data source. *

It is generally used like this: *

 * MediaExtractor extractor = new MediaExtractor();
 * extractor.setDataSource(...);
 * int numTracks = extractor.getTrackCount();
 * for (int i = 0; i < numTracks; ++i) {
 *   MediaFormat format = extractor.getTrackFormat(i);
 *   String mime = format.getString(MediaFormat.KEY_MIME);
 *   if (weAreInterestedInThisTrack) {
 *     extractor.selectTrack(i);
 *   }
 * }
 * ByteBuffer inputBuffer = ByteBuffer.allocate(...)
 * while (extractor.readSampleData(inputBuffer, ...) >= 0) {
 *   int trackIndex = extractor.getSampleTrackIndex();
 *   long presentationTimeUs = extractor.getSampleTime();
 *   ...
 *   extractor.advance();
 * }
 *
 * extractor.release();
 * extractor = null;
 * 
*/ final public class MediaExtractor { public MediaExtractor() { native_setup(); } /** * Sets the DataSource object to be used as the data source for this extractor * {@hide} */ public native final void setDataSource(DataSource source) throws IOException; /** * Sets the data source as a content Uri. * * @param context the Context to use when resolving the Uri * @param uri the Content URI of the data you want to extract from. * @param headers the headers to be sent together with the request for the data */ public final void setDataSource( Context context, Uri uri, Map headers) throws IOException { String scheme = uri.getScheme(); if(scheme == null || scheme.equals("file")) { setDataSource(uri.getPath()); return; } AssetFileDescriptor fd = null; try { ContentResolver resolver = context.getContentResolver(); fd = resolver.openAssetFileDescriptor(uri, "r"); if (fd == null) { return; } // Note: using getDeclaredLength so that our behavior is the same // as previous versions when the content provider is returning // a full file. if (fd.getDeclaredLength() < 0) { setDataSource(fd.getFileDescriptor()); } else { setDataSource( fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength()); } return; } catch (SecurityException ex) { } catch (IOException ex) { } finally { if (fd != null) { fd.close(); } } setDataSource(uri.toString(), headers); } /** * Sets the data source (file-path or http URL) to use. * * @param path the path of the file, or the http URL * @param headers the headers associated with the http request for the stream you want to play */ public final void setDataSource(String path, Map headers) throws IOException { String[] keys = null; String[] values = null; if (headers != null) { keys = new String[headers.size()]; values = new String[headers.size()]; int i = 0; for (Map.Entry entry: headers.entrySet()) { keys[i] = entry.getKey(); values[i] = entry.getValue(); ++i; } } nativeSetDataSource( MediaHTTPService.createHttpServiceBinderIfNecessary(path), path, keys, values); } private native final void nativeSetDataSource( IBinder httpServiceBinder, String path, String[] keys, String[] values) throws IOException; /** * Sets the data source (file-path or http URL) to use. * * @param path the path of the file, or the http URL of the stream * *

When path refers to a local file, the file may actually be opened by a * process other than the calling application. This implies that the pathname * should be an absolute path (as any other process runs with unspecified current working * directory), and that the pathname should reference a world-readable file. * As an alternative, the application could first open the file for reading, * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}. */ public final void setDataSource(String path) throws IOException { nativeSetDataSource( MediaHTTPService.createHttpServiceBinderIfNecessary(path), path, null, null); } /** * Sets the data source (FileDescriptor) to use. It is the caller's responsibility * to close the file descriptor. It is safe to do so as soon as this call returns. * * @param fd the FileDescriptor for the file you want to extract from. */ public final void setDataSource(FileDescriptor fd) throws IOException { setDataSource(fd, 0, 0x7ffffffffffffffL); } /** * Sets the data source (FileDescriptor) to use. The FileDescriptor must be * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility * to close the file descriptor. It is safe to do so as soon as this call returns. * * @param fd the FileDescriptor for the file you want to extract from. * @param offset the offset into the file where the data to be extracted starts, in bytes * @param length the length in bytes of the data to be extracted */ public native final void setDataSource( FileDescriptor fd, long offset, long length) throws IOException; @Override protected void finalize() { native_finalize(); } /** * Make sure you call this when you're done to free up any resources * instead of relying on the garbage collector to do this for you at * some point in the future. */ public native final void release(); /** * Count the number of tracks found in the data source. */ public native final int getTrackCount(); /** * Get the PSSH info if present. * @return a map of uuid-to-bytes, with the uuid specifying * the crypto scheme, and the bytes being the data specific to that scheme. */ public Map getPsshInfo() { Map psshMap = null; Map formatMap = getFileFormatNative(); if (formatMap != null && formatMap.containsKey("pssh")) { ByteBuffer rawpssh = (ByteBuffer) formatMap.get("pssh"); rawpssh.order(ByteOrder.nativeOrder()); rawpssh.rewind(); formatMap.remove("pssh"); // parse the flat pssh bytebuffer into something more manageable psshMap = new HashMap(); while (rawpssh.remaining() > 0) { rawpssh.order(ByteOrder.BIG_ENDIAN); long msb = rawpssh.getLong(); long lsb = rawpssh.getLong(); UUID uuid = new UUID(msb, lsb); rawpssh.order(ByteOrder.nativeOrder()); int datalen = rawpssh.getInt(); byte [] psshdata = new byte[datalen]; rawpssh.get(psshdata); psshMap.put(uuid, psshdata); } } return psshMap; } private native Map getFileFormatNative(); /** * Get the track format at the specified index. * More detail on the representation can be found at {@link android.media.MediaCodec} */ public MediaFormat getTrackFormat(int index) { return new MediaFormat(getTrackFormatNative(index)); } private native Map getTrackFormatNative(int index); /** * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and * {@link #getSampleTime} only retrieve information for the subset of tracks * selected. * Selecting the same track multiple times has no effect, the track is * only selected once. */ public native void selectTrack(int index); /** * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and * {@link #getSampleTime} only retrieve information for the subset of tracks * selected. */ public native void unselectTrack(int index); /** * If possible, seek to a sync sample at or before the specified time */ public static final int SEEK_TO_PREVIOUS_SYNC = 0; /** * If possible, seek to a sync sample at or after the specified time */ public static final int SEEK_TO_NEXT_SYNC = 1; /** * If possible, seek to the sync sample closest to the specified time */ public static final int SEEK_TO_CLOSEST_SYNC = 2; /** * All selected tracks seek near the requested time according to the * specified mode. */ public native void seekTo(long timeUs, int mode); /** * Advance to the next sample. Returns false if no more sample data * is available (end of stream). */ public native boolean advance(); /** * Retrieve the current encoded sample and store it in the byte buffer * starting at the given offset. *

* Note:As of API 21, on success the position and limit of * {@code byteBuf} is updated to point to the data just read. * @param byteBuf the destination byte buffer * @return the sample size (or -1 if no more samples are available). */ public native int readSampleData(ByteBuffer byteBuf, int offset); /** * Returns the track index the current sample originates from (or -1 * if no more samples are available) */ public native int getSampleTrackIndex(); /** * Returns the current sample's presentation time in microseconds. * or -1 if no more samples are available. */ public native long getSampleTime(); // Keep these in sync with their equivalents in NuMediaExtractor.h /** * The sample is a sync sample (or in {@link MediaCodec}'s terminology * it is a key frame.) * * @see MediaCodec#BUFFER_FLAG_KEY_FRAME */ public static final int SAMPLE_FLAG_SYNC = 1; /** * The sample is (at least partially) encrypted, see also the documentation * for {@link android.media.MediaCodec#queueSecureInputBuffer} */ public static final int SAMPLE_FLAG_ENCRYPTED = 2; /** * Returns the current sample's flags. */ public native int getSampleFlags(); /** * If the sample flags indicate that the current sample is at least * partially encrypted, this call returns relevant information about * the structure of the sample data required for decryption. * @param info The android.media.MediaCodec.CryptoInfo structure * to be filled in. * @return true iff the sample flags contain {@link #SAMPLE_FLAG_ENCRYPTED} */ public native boolean getSampleCryptoInfo(MediaCodec.CryptoInfo info); /** * Returns an estimate of how much data is presently cached in memory * expressed in microseconds. Returns -1 if that information is unavailable * or not applicable (no cache). */ public native long getCachedDuration(); /** * Returns true iff we are caching data and the cache has reached the * end of the data stream (for now, a future seek may of course restart * the fetching of data). * This API only returns a meaningful result if {@link #getCachedDuration} * indicates the presence of a cache, i.e. does NOT return -1. */ public native boolean hasCacheReachedEndOfStream(); private static native final void native_init(); private native final void native_setup(); private native final void native_finalize(); static { System.loadLibrary("media_jni"); native_init(); } private long mNativeContext; }