843 lines
31 KiB
Java
843 lines
31 KiB
Java
/*
|
|
* 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.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.res.AssetFileDescriptor;
|
|
import android.media.metrics.LogSessionId;
|
|
import android.net.Uri;
|
|
import android.os.IBinder;
|
|
import android.os.IHwBinder;
|
|
import android.os.PersistableBundle;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.IOException;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* MediaExtractor facilitates extraction of demuxed, typically encoded, media data
|
|
* from a data source.
|
|
* <p>It is generally used like this:
|
|
* <pre>
|
|
* 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;
|
|
* </pre>
|
|
*
|
|
* <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
|
|
* when used with network-based content.
|
|
*/
|
|
public final class MediaExtractor {
|
|
public MediaExtractor() {
|
|
native_setup();
|
|
}
|
|
|
|
/**
|
|
* Sets the data source (MediaDataSource) to use.
|
|
*
|
|
* @param dataSource the MediaDataSource for the media you want to extract from
|
|
*
|
|
* @throws IllegalArgumentException if dataSource is invalid.
|
|
*/
|
|
public native final void setDataSource(@NonNull MediaDataSource dataSource)
|
|
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.
|
|
*
|
|
* <p>When <code>uri</code> refers to a network file the
|
|
* {@link android.Manifest.permission#INTERNET} permission is required.
|
|
*
|
|
* @param headers the headers to be sent together with the request for the data.
|
|
* This can be {@code null} if no specific headers are to be sent with the
|
|
* request.
|
|
*/
|
|
public final void setDataSource(
|
|
@NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> 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
|
|
*
|
|
* <p>When <code>path</code> refers to a network file the
|
|
* {@link android.Manifest.permission#INTERNET} permission is required.
|
|
*
|
|
* @param headers the headers associated with the http request for the stream you want to play.
|
|
* This can be {@code null} if no specific headers are to be sent with the
|
|
* request.
|
|
*/
|
|
public final void setDataSource(@NonNull String path, @Nullable Map<String, String> 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<String, String> entry: headers.entrySet()) {
|
|
keys[i] = entry.getKey();
|
|
values[i] = entry.getValue();
|
|
++i;
|
|
}
|
|
}
|
|
|
|
nativeSetDataSource(
|
|
MediaHTTPService.createHttpServiceBinderIfNecessary(path),
|
|
path,
|
|
keys,
|
|
values);
|
|
}
|
|
|
|
private native final void nativeSetDataSource(
|
|
@NonNull IBinder httpServiceBinder,
|
|
@NonNull String path,
|
|
@Nullable String[] keys,
|
|
@Nullable 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
|
|
*
|
|
* <p>When <code>path</code> 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)}.
|
|
*
|
|
* <p>When <code>path</code> refers to a network file the
|
|
* {@link android.Manifest.permission#INTERNET} permission is required.
|
|
*/
|
|
public final void setDataSource(@NonNull String path) throws IOException {
|
|
nativeSetDataSource(
|
|
MediaHTTPService.createHttpServiceBinderIfNecessary(path),
|
|
path,
|
|
null,
|
|
null);
|
|
}
|
|
|
|
/**
|
|
* Sets the data source (AssetFileDescriptor) 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 afd the AssetFileDescriptor for the file you want to extract from.
|
|
*/
|
|
public final void setDataSource(@NonNull AssetFileDescriptor afd)
|
|
throws IOException, IllegalArgumentException, IllegalStateException {
|
|
Preconditions.checkNotNull(afd);
|
|
// Note: using getDeclaredLength so that our behavior is the same
|
|
// as previous versions when the content provider is returning
|
|
// a full file.
|
|
if (afd.getDeclaredLength() < 0) {
|
|
setDataSource(afd.getFileDescriptor());
|
|
} else {
|
|
setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(@NonNull 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(
|
|
@NonNull FileDescriptor fd, long offset, long length) throws IOException;
|
|
|
|
/**
|
|
* Sets the MediaCas instance to use. This should be called after a successful setDataSource()
|
|
* if at least one track reports mime type of
|
|
* {@link android.media.MediaFormat#MIMETYPE_AUDIO_SCRAMBLED} or
|
|
* {@link android.media.MediaFormat#MIMETYPE_VIDEO_SCRAMBLED}. Stream parsing will not proceed
|
|
* until a valid MediaCas object is provided.
|
|
*
|
|
* @param mediaCas the MediaCas object to use.
|
|
* @deprecated Use the {@code Descrambler} system API instead, or DRM public APIs like
|
|
* {@link MediaDrm}.
|
|
*/
|
|
@Deprecated
|
|
public final void setMediaCas(@NonNull MediaCas mediaCas) {
|
|
mMediaCas = mediaCas;
|
|
nativeSetMediaCas(mediaCas.getBinder());
|
|
}
|
|
|
|
private native final void nativeSetMediaCas(@NonNull IHwBinder casBinder);
|
|
|
|
/**
|
|
* Describes the conditional access system used to scramble a track.
|
|
*/
|
|
public static final class CasInfo {
|
|
private final int mSystemId;
|
|
private final MediaCas.Session mSession;
|
|
private final byte[] mPrivateData;
|
|
|
|
CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData) {
|
|
mSystemId = systemId;
|
|
mSession = session;
|
|
mPrivateData = privateData;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the system id of the conditional access system.
|
|
*
|
|
* @return CA system id of the CAS used to scramble the track.
|
|
*/
|
|
public int getSystemId() {
|
|
return mSystemId;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the private data in the CA_Descriptor associated with a track.
|
|
* Some CAS systems may need this to initialize the CAS plugin object. This
|
|
* private data can only be retrieved before a valid {@link MediaCas} object
|
|
* is set on the extractor.
|
|
* <p>
|
|
* @see MediaExtractor#setMediaCas
|
|
* <p>
|
|
* @return a byte array containing the private data. A null return value
|
|
* indicates that the private data is unavailable. An empty array,
|
|
* on the other hand, indicates that the private data is empty
|
|
* (zero in length).
|
|
*/
|
|
@Nullable
|
|
public byte[] getPrivateData() {
|
|
return mPrivateData;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the {@link MediaCas.Session} associated with a track. The
|
|
* session is needed to initialize a descrambler in order to decode the
|
|
* scrambled track. The session object can only be retrieved after a valid
|
|
* {@link MediaCas} object is set on the extractor.
|
|
* <p>
|
|
* @see MediaExtractor#setMediaCas
|
|
* @see MediaDescrambler#setMediaCasSession
|
|
* <p>
|
|
* @return a {@link MediaCas.Session} object associated with a track.
|
|
*/
|
|
public MediaCas.Session getSession() {
|
|
return mSession;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the information about the conditional access system used to scramble
|
|
* a track.
|
|
*
|
|
* @param index of the track.
|
|
* @return an {@link CasInfo} object describing the conditional access system.
|
|
*/
|
|
public CasInfo getCasInfo(int index) {
|
|
Map<String, Object> formatMap = getTrackFormatNative(index);
|
|
if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
|
|
int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
|
|
MediaCas.Session session = null;
|
|
byte[] privateData = null;
|
|
if (formatMap.containsKey(MediaFormat.KEY_CA_PRIVATE_DATA)) {
|
|
ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_PRIVATE_DATA);
|
|
buf.rewind();
|
|
privateData = new byte[buf.remaining()];
|
|
buf.get(privateData);
|
|
}
|
|
if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
|
|
ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
|
|
buf.rewind();
|
|
final byte[] sessionId = new byte[buf.remaining()];
|
|
buf.get(sessionId);
|
|
session = mMediaCas.createFromSessionId(sessionId);
|
|
}
|
|
return new CasInfo(systemId, session, privateData);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@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();
|
|
|
|
/**
|
|
* Extract DRM initialization data if it exists
|
|
*
|
|
* @return DRM initialization data in the content, or {@code null}
|
|
* if no recognizable DRM format is found;
|
|
* @see DrmInitData
|
|
*/
|
|
public DrmInitData getDrmInitData() {
|
|
Map<String, Object> formatMap = getFileFormatNative();
|
|
if (formatMap == null) {
|
|
return null;
|
|
}
|
|
if (formatMap.containsKey("pssh")) {
|
|
Map<UUID, byte[]> psshMap = getPsshInfo();
|
|
DrmInitData.SchemeInitData[] schemeInitDatas =
|
|
psshMap.entrySet().stream().map(
|
|
entry -> new DrmInitData.SchemeInitData(
|
|
entry.getKey(), /* mimeType= */ "cenc", entry.getValue()))
|
|
.toArray(DrmInitData.SchemeInitData[]::new);
|
|
final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
|
|
Arrays.stream(schemeInitDatas).collect(
|
|
Collectors.toMap(initData -> initData.uuid, initData -> initData));
|
|
return new DrmInitData() {
|
|
public SchemeInitData get(UUID schemeUuid) {
|
|
return initDataMap.get(schemeUuid);
|
|
}
|
|
|
|
@Override
|
|
public int getSchemeInitDataCount() {
|
|
return schemeInitDatas.length;
|
|
}
|
|
|
|
@Override
|
|
public SchemeInitData getSchemeInitDataAt(int index) {
|
|
return schemeInitDatas[index];
|
|
}
|
|
};
|
|
} else {
|
|
int numTracks = getTrackCount();
|
|
for (int i = 0; i < numTracks; ++i) {
|
|
Map<String, Object> trackFormatMap = getTrackFormatNative(i);
|
|
if (!trackFormatMap.containsKey("crypto-key")) {
|
|
continue;
|
|
}
|
|
ByteBuffer buf = (ByteBuffer) trackFormatMap.get("crypto-key");
|
|
buf.rewind();
|
|
final byte[] data = new byte[buf.remaining()];
|
|
buf.get(data);
|
|
// Webm scheme init data is not uuid-specific.
|
|
DrmInitData.SchemeInitData webmSchemeInitData =
|
|
new DrmInitData.SchemeInitData(
|
|
DrmInitData.SchemeInitData.UUID_NIL, "webm", data);
|
|
return new DrmInitData() {
|
|
public SchemeInitData get(UUID schemeUuid) {
|
|
return webmSchemeInitData;
|
|
}
|
|
|
|
@Override
|
|
public int getSchemeInitDataCount() {
|
|
return 1;
|
|
}
|
|
|
|
@Override
|
|
public SchemeInitData getSchemeInitDataAt(int index) {
|
|
return webmSchemeInitData;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the list of available audio presentations for the track.
|
|
* @param trackIndex index of the track.
|
|
* @return a list of available audio presentations for a given valid audio track index.
|
|
* The list will be empty if the source does not contain any audio presentations.
|
|
*/
|
|
@NonNull
|
|
public List<AudioPresentation> getAudioPresentations(int trackIndex) {
|
|
return native_getAudioPresentations(trackIndex);
|
|
}
|
|
|
|
@NonNull
|
|
private native List<AudioPresentation> native_getAudioPresentations(int trackIndex);
|
|
|
|
/**
|
|
* 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.
|
|
* This can be {@code null} if the source does not contain PSSH info.
|
|
*/
|
|
@Nullable
|
|
public Map<UUID, byte[]> getPsshInfo() {
|
|
Map<UUID, byte[]> psshMap = null;
|
|
Map<String, Object> 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<UUID, byte[]>();
|
|
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;
|
|
}
|
|
|
|
@NonNull
|
|
private native Map<String, Object> getFileFormatNative();
|
|
|
|
/**
|
|
* Get the track format at the specified index.
|
|
*
|
|
* More detail on the representation can be found at {@link android.media.MediaCodec}
|
|
* <p>
|
|
* The following table summarizes support for format keys across android releases:
|
|
*
|
|
* <table style="width: 0%">
|
|
* <thead>
|
|
* <tr>
|
|
* <th rowspan=2>OS Version(s)</th>
|
|
* <td colspan=3>{@code MediaFormat} keys used for</th>
|
|
* </tr><tr>
|
|
* <th>All Tracks</th>
|
|
* <th>Audio Tracks</th>
|
|
* <th>Video Tracks</th>
|
|
* </tr>
|
|
* </thead>
|
|
* <tbody>
|
|
* <tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN}</td>
|
|
* <td rowspan=8>{@link MediaFormat#KEY_MIME},<br>
|
|
* {@link MediaFormat#KEY_DURATION},<br>
|
|
* {@link MediaFormat#KEY_MAX_INPUT_SIZE}</td>
|
|
* <td rowspan=5>{@link MediaFormat#KEY_SAMPLE_RATE},<br>
|
|
* {@link MediaFormat#KEY_CHANNEL_COUNT},<br>
|
|
* {@link MediaFormat#KEY_CHANNEL_MASK},<br>
|
|
* gapless playback information<sup>.mp3, .mp4</sup>,<br>
|
|
* {@link MediaFormat#KEY_IS_ADTS}<sup>AAC if streaming</sup>,<br>
|
|
* codec-specific data<sup>AAC, Vorbis</sup></td>
|
|
* <td rowspan=2>{@link MediaFormat#KEY_WIDTH},<br>
|
|
* {@link MediaFormat#KEY_HEIGHT},<br>
|
|
* codec-specific data<sup>AVC, MPEG4</sup></td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}</td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td>
|
|
* <td rowspan=3>as above, plus<br>
|
|
* Pixel aspect ratio information<sup>AVC, *</sup></td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td>
|
|
* <td rowspan=2>as above, plus<br>
|
|
* {@link MediaFormat#KEY_BIT_RATE}<sup>AAC</sup>,<br>
|
|
* codec-specific data<sup>Opus</sup></td>
|
|
* <td rowspan=2>as above, plus<br>
|
|
* {@link MediaFormat#KEY_ROTATION}<sup>.mp4</sup>,<br>
|
|
* {@link MediaFormat#KEY_BIT_RATE}<sup>MPEG4</sup>,<br>
|
|
* codec-specific data<sup>HEVC</sup></td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#M}</td>
|
|
* <td>as above, plus<br>
|
|
* gapless playback information<sup>Opus</sup></td>
|
|
* <td>as above, plus<br>
|
|
* {@link MediaFormat#KEY_FRAME_RATE} (integer)</td>
|
|
* </tr><tr>
|
|
* <td>{@link android.os.Build.VERSION_CODES#N}</td>
|
|
* <td>as above, plus<br>
|
|
* {@link MediaFormat#KEY_TRACK_ID},<br>
|
|
* <!-- {link MediaFormat#KEY_MAX_BIT_RATE}<sup>#, .mp4</sup>,<br> -->
|
|
* {@link MediaFormat#KEY_BIT_RATE}<sup>#, .mp4</sup></td>
|
|
* <td>as above, plus<br>
|
|
* {@link MediaFormat#KEY_PCM_ENCODING},<br>
|
|
* {@link MediaFormat#KEY_PROFILE}<sup>AAC</sup></td>
|
|
* <td>as above, plus<br>
|
|
* {@link MediaFormat#KEY_HDR_STATIC_INFO}<sup>#, .webm</sup>,<br>
|
|
* {@link MediaFormat#KEY_COLOR_STANDARD}<sup>#</sup>,<br>
|
|
* {@link MediaFormat#KEY_COLOR_TRANSFER}<sup>#</sup>,<br>
|
|
* {@link MediaFormat#KEY_COLOR_RANGE}<sup>#</sup>,<br>
|
|
* {@link MediaFormat#KEY_PROFILE}<sup>MPEG2, H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
|
|
* {@link MediaFormat#KEY_LEVEL}<sup>H.263, MPEG4, AVC, HEVC, VP9</sup>,<br>
|
|
* codec-specific data<sup>VP9</sup></td>
|
|
* </tr>
|
|
* <tr>
|
|
* <td colspan=4>
|
|
* <p class=note><strong>Notes:</strong><br>
|
|
* #: container-specified value only.<br>
|
|
* .mp4, .webm…: for listed containers<br>
|
|
* MPEG4, AAC…: for listed codecs
|
|
* </td>
|
|
* </tr><tr>
|
|
* <td colspan=4>
|
|
* <p class=note>Note that that level information contained in the container many times
|
|
* does not match the level of the actual bitstream. You may want to clear the level using
|
|
* {@code MediaFormat.setString(KEY_LEVEL, null)} before using the track format to find a
|
|
* decoder that can play back a particular track.
|
|
* </td>
|
|
* </tr><tr>
|
|
* <td colspan=4>
|
|
* <p class=note><strong>*Pixel (sample) aspect ratio</strong> is returned in the following
|
|
* keys. The display width can be calculated for example as:
|
|
* <p align=center>
|
|
* display-width = display-height * crop-width / crop-height * sar-width / sar-height
|
|
* </td>
|
|
* </tr><tr>
|
|
* <th>Format Key</th><th>Value Type</th><th colspan=2>Description</th>
|
|
* </tr><tr>
|
|
* <td>{@code "sar-width"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio width</td>
|
|
* </tr><tr>
|
|
* <td>{@code "sar-height"}</td><td>Integer</td><td colspan=2>Pixel aspect ratio height</td>
|
|
* </tr>
|
|
* </tbody>
|
|
* </table>
|
|
*
|
|
*/
|
|
@NonNull
|
|
public MediaFormat getTrackFormat(int index) {
|
|
return new MediaFormat(getTrackFormatNative(index));
|
|
}
|
|
|
|
@NonNull
|
|
private native Map<String, Object> 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;
|
|
|
|
/** @hide */
|
|
@IntDef({
|
|
SEEK_TO_PREVIOUS_SYNC,
|
|
SEEK_TO_NEXT_SYNC,
|
|
SEEK_TO_CLOSEST_SYNC,
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface SeekMode {}
|
|
|
|
/**
|
|
* All selected tracks seek near the requested time according to the
|
|
* specified mode.
|
|
*/
|
|
public native void seekTo(long timeUs, @SeekMode int mode);
|
|
|
|
/**
|
|
* Advance to the next sample. Returns false if no more sample data
|
|
* is available (end of stream).
|
|
*
|
|
* When extracting a local file, the behaviors of {@link #advance} and
|
|
* {@link #readSampleData} are undefined in presence of concurrent
|
|
* writes to the same local file; more specifically, end of stream
|
|
* could be signalled earlier than expected.
|
|
*/
|
|
public native boolean advance();
|
|
|
|
/**
|
|
* Retrieve the current encoded sample and store it in the byte buffer
|
|
* starting at the given offset.
|
|
* <p>
|
|
* <b>Note:</b>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(@NonNull 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();
|
|
|
|
/**
|
|
* @return size of the current sample in bytes or -1 if no more
|
|
* samples are available.
|
|
*/
|
|
public native long getSampleSize();
|
|
|
|
// 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;
|
|
|
|
/**
|
|
* This indicates that the buffer only contains part of a frame,
|
|
* and the decoder should batch the data until a buffer without
|
|
* this flag appears before decoding the frame.
|
|
*
|
|
* @see MediaCodec#BUFFER_FLAG_PARTIAL_FRAME
|
|
*/
|
|
public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4;
|
|
|
|
/** @hide */
|
|
@IntDef(
|
|
flag = true,
|
|
value = {
|
|
SAMPLE_FLAG_SYNC,
|
|
SAMPLE_FLAG_ENCRYPTED,
|
|
SAMPLE_FLAG_PARTIAL_FRAME,
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface SampleFlag {}
|
|
|
|
/**
|
|
* Returns the current sample's flags.
|
|
*/
|
|
@SampleFlag
|
|
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(@NonNull 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();
|
|
|
|
/**
|
|
* Sets the {@link LogSessionId} for MediaExtractor.
|
|
*/
|
|
public void setLogSessionId(@NonNull LogSessionId logSessionId) {
|
|
mLogSessionId = Objects.requireNonNull(logSessionId);
|
|
native_setLogSessionId(logSessionId.getStringId());
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link LogSessionId} for MediaExtractor.
|
|
*/
|
|
@NonNull
|
|
public LogSessionId getLogSessionId() {
|
|
return mLogSessionId;
|
|
}
|
|
|
|
/**
|
|
* Return Metrics data about the current media container.
|
|
*
|
|
* @return a {@link PersistableBundle} containing the set of attributes and values
|
|
* available for the media container being handled by this instance
|
|
* of MediaExtractor.
|
|
* The attributes are descibed in {@link MetricsConstants}.
|
|
*
|
|
* Additional vendor-specific fields may also be present in
|
|
* the return value.
|
|
*/
|
|
|
|
public PersistableBundle getMetrics() {
|
|
PersistableBundle bundle = native_getMetrics();
|
|
return bundle;
|
|
}
|
|
|
|
private native void native_setLogSessionId(String logSessionId);
|
|
private native PersistableBundle native_getMetrics();
|
|
|
|
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 MediaCas mMediaCas;
|
|
@NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
|
|
|
|
private long mNativeContext;
|
|
|
|
public final static class MetricsConstants
|
|
{
|
|
private MetricsConstants() {}
|
|
|
|
/**
|
|
* Key to extract the container format
|
|
* from the {@link MediaExtractor#getMetrics} return value.
|
|
* The value is a String.
|
|
*/
|
|
public static final String FORMAT = "android.media.mediaextractor.fmt";
|
|
|
|
/**
|
|
* Key to extract the container MIME type
|
|
* from the {@link MediaExtractor#getMetrics} return value.
|
|
* The value is a String.
|
|
*/
|
|
public static final String MIME_TYPE = "android.media.mediaextractor.mime";
|
|
|
|
/**
|
|
* Key to extract the number of tracks in the container
|
|
* from the {@link MediaExtractor#getMetrics} return value.
|
|
* The value is an integer.
|
|
*/
|
|
public static final String TRACKS = "android.media.mediaextractor.ntrk";
|
|
|
|
}
|
|
|
|
}
|