MediaCas: address comments for API reviews

- Wrap session id byte array in Session object

- Move session operations from MediaCas to Session

- Remove position prarameters on descramble() method

- Retrieve cas info for a track by getCasInfo() instead
  of getDrmInitData().

bug: 22804304
bug: 36791613
bug: 36783335

Change-Id: Ib3ad8d6a2f679c0e60d2bb025ac5999339722306
This commit is contained in:
Chong Zhang
2017-03-31 14:52:52 -07:00
parent 0015a15353
commit addc39ec27
8 changed files with 299 additions and 193 deletions

View File

@@ -21818,24 +21818,20 @@ package android.media {
field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
}
public final class MediaCas {
public final class MediaCas implements java.lang.AutoCloseable {
ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public void close();
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
method public byte[] openSession(int) throws android.media.MediaCasException;
method public byte[] openSession(int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int, int) throws android.media.MediaCasException;
method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
method public void processEmm(byte[]) throws android.media.MediaCasException;
method public void provision(java.lang.String) throws android.media.MediaCasException;
method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21847,6 +21843,13 @@ package android.media {
method public int getSystemId();
}
public final class MediaCas.Session implements java.lang.AutoCloseable {
method public void close();
method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[]) throws android.media.MediaCasException;
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
}
public class MediaCasException extends java.lang.Exception {
}
@@ -22290,12 +22293,12 @@ package android.media {
method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
}
public final class MediaDescrambler {
public final class MediaDescrambler implements java.lang.AutoCloseable {
ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public void close();
method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
method public final boolean requiresSecureDecoderComponent(java.lang.String);
method public final void setMediaCasSession(byte[]);
method public final void setMediaCasSession(android.media.MediaCas.Session);
}
public class MediaDescription implements android.os.Parcelable {
@@ -22433,6 +22436,7 @@ package android.media {
ctor public MediaExtractor();
method public boolean advance();
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
method public android.media.MediaMetricsSet getMetrics();
method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -22464,6 +22468,11 @@ package android.media {
field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
}
public static final class MediaExtractor.CasInfo {
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
public final class MediaFormat {
ctor public MediaFormat();
method public final boolean containsKey(java.lang.String);

View File

@@ -23643,24 +23643,20 @@ package android.media {
field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
}
public final class MediaCas {
public final class MediaCas implements java.lang.AutoCloseable {
ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public void close();
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
method public byte[] openSession(int) throws android.media.MediaCasException;
method public byte[] openSession(int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int, int) throws android.media.MediaCasException;
method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
method public void processEmm(byte[]) throws android.media.MediaCasException;
method public void provision(java.lang.String) throws android.media.MediaCasException;
method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -23672,6 +23668,13 @@ package android.media {
method public int getSystemId();
}
public final class MediaCas.Session implements java.lang.AutoCloseable {
method public void close();
method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[]) throws android.media.MediaCasException;
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
}
public class MediaCasException extends java.lang.Exception {
}
@@ -24115,12 +24118,12 @@ package android.media {
method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
}
public final class MediaDescrambler {
public final class MediaDescrambler implements java.lang.AutoCloseable {
ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public void close();
method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
method public final boolean requiresSecureDecoderComponent(java.lang.String);
method public final void setMediaCasSession(byte[]);
method public final void setMediaCasSession(android.media.MediaCas.Session);
}
public class MediaDescription implements android.os.Parcelable {
@@ -24258,6 +24261,7 @@ package android.media {
ctor public MediaExtractor();
method public boolean advance();
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
method public android.media.MediaMetricsSet getMetrics();
method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -24289,6 +24293,11 @@ package android.media {
field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
}
public static final class MediaExtractor.CasInfo {
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
public final class MediaFormat {
ctor public MediaFormat();
method public final boolean containsKey(java.lang.String);

View File

@@ -21931,24 +21931,20 @@ package android.media {
field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
}
public final class MediaCas {
public final class MediaCas implements java.lang.AutoCloseable {
ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public void close();
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
method public byte[] openSession(int) throws android.media.MediaCasException;
method public byte[] openSession(int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int) throws android.media.MediaCasException;
method public android.media.MediaCas.Session openSession(int, int) throws android.media.MediaCasException;
method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
method public void processEmm(byte[]) throws android.media.MediaCasException;
method public void provision(java.lang.String) throws android.media.MediaCasException;
method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21960,6 +21956,13 @@ package android.media {
method public int getSystemId();
}
public final class MediaCas.Session implements java.lang.AutoCloseable {
method public void close();
method public void processEcm(byte[], int, int) throws android.media.MediaCasException;
method public void processEcm(byte[]) throws android.media.MediaCasException;
method public void setPrivateData(byte[]) throws android.media.MediaCasException;
}
public class MediaCasException extends java.lang.Exception {
}
@@ -22403,12 +22406,12 @@ package android.media {
method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
}
public final class MediaDescrambler {
public final class MediaDescrambler implements java.lang.AutoCloseable {
ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public void close();
method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo);
method public final boolean requiresSecureDecoderComponent(java.lang.String);
method public final void setMediaCasSession(byte[]);
method public final void setMediaCasSession(android.media.MediaCas.Session);
}
public class MediaDescription implements android.os.Parcelable {
@@ -22546,6 +22549,7 @@ package android.media {
ctor public MediaExtractor();
method public boolean advance();
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
method public android.media.MediaMetricsSet getMetrics();
method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
@@ -22577,6 +22581,11 @@ package android.media {
field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
}
public static final class MediaExtractor.CasInfo {
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
public final class MediaFormat {
ctor public MediaFormat();
method public final boolean containsKey(java.lang.String);

View File

@@ -51,12 +51,13 @@ import android.util.Singleton;
* management messages) can be distributed out-of-band, or in-band with the stream.
* <p>
* To descramble elementary streams, the app first calls {@link #openSession} to
* generate a sessionId that will uniquely identify a session. A session provides
* a context for subsequent key updates and descrambling activities. The ECMs
* (Entitlement control messages) are sent to the session via method {@link #processEcm}.
* generate a {@link Session} object that will uniquely identify a session. A session
* provides a context for subsequent key updates and descrambling activities. The ECMs
* (Entitlement control messages) are sent to the session via method
* {@link Session#processEcm}.
* <p>
* The app next constructs a MediaDescrambler object, and initializes it with the
* sessionId using {@link MediaDescrambler#setMediaCasSession}. This ties the
* session using {@link MediaDescrambler#setMediaCasSession}. This ties the
* descrambler to the session, and the descrambler can then be used to descramble
* content secured with the session's key, either during extraction, or during decoding
* with {@link android.media.MediaCodec}.
@@ -79,19 +80,20 @@ import android.util.Singleton;
* If the app uses {@link MediaExtractor}, it can delegate the CAS session
* management to MediaExtractor by calling {@link MediaExtractor#setMediaCas}.
* MediaExtractor will take over and call {@link #openSession}, {@link #processEmm}
* and/or {@link #processEcm}, etc.. if necessary.
* and/or {@link Session#processEcm}, etc.. if necessary.
* <p>
* When using {@link MediaExtractor}, the app would still need a MediaDescrambler
* to use with {@link MediaCodec} if the licensing requires a secure decoder. The
* sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
* and used to initialize a MediaDescrambler object for MediaCodec.
* session associated with the descrambler of a track can be retrieved by calling
* {@link MediaExtractor#getCasInfo}, and used to initialize a MediaDescrambler
* object for MediaCodec.
* <p>
* <h3>Listeners</h3>
* <p>The app may register a listener to receive events from the CA system using
* method {@link #setEventListener}. The exact format of the event is scheme-specific
* and is not specified by this API.
*/
public final class MediaCas {
public final class MediaCas implements AutoCloseable {
private static final String TAG = "MediaCas";
private final ParcelableCasData mCasData = new ParcelableCasData();
private ICas mICas;
@@ -228,6 +230,106 @@ public final class MediaCas {
}
}
/**
* Class for an open session with the CA system.
*/
public final class Session implements AutoCloseable {
final byte[] mSessionId;
Session(@NonNull byte[] sessionId) {
mSessionId = sessionId;
}
/**
* Set the private data for a session.
*
* @param data byte array of the private data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void setPrivateData(@NonNull byte[] data)
throws MediaCasException {
validateInternalStates();
try {
mICas.setSessionPrivateData(mSessionId, data);
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
/**
* Send a received ECM packet to the specified session of the CA system.
*
* @param data byte array of the ECM data.
* @param offset position within data where the ECM data begins.
* @param length length of the data (starting from offset).
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void processEcm(@NonNull byte[] data, int offset, int length)
throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEcm(mSessionId, mCasData);
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
/**
* Send a received ECM packet to the specified session of the CA system.
* This is similar to {@link Session#processEcm(byte[], int, int)}
* except that the entire byte array is sent.
*
* @param data byte array of the ECM data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void processEcm(@NonNull byte[] data) throws MediaCasException {
processEcm(data, 0, data.length);
}
/**
* Close the session.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
@Override
public void close() {
validateInternalStates();
try {
mICas.closeSession(mSessionId);
} catch (ServiceSpecificException e) {
MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
}
Session createFromSessionId(byte[] sessionId) {
if (sessionId == null || sessionId.length == 0) {
return null;
}
return new Session(sessionId);
}
/**
* Class for parceling CAS plugin descriptors over IMediaCasService binder.
*/
@@ -408,17 +510,17 @@ public final class MediaCas {
*
* @param programNumber program_number of the program (as in ISO/IEC13818-1).
*
* @return session id of the newly opened session.
* @return session the newly opened session.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public byte[] openSession(int programNumber) throws MediaCasException {
public Session openSession(int programNumber) throws MediaCasException {
validateInternalStates();
try {
return mICas.openSession(programNumber);
return createFromSessionId(mICas.openSession(programNumber));
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
@@ -433,18 +535,18 @@ public final class MediaCas {
* @param programNumber program_number of the stream (as in ISO/IEC13818-1).
* @param elementaryPID elementary_PID of the stream (as in ISO/IEC13818-1).
*
* @return session id of the newly opened session.
* @return session the newly opened session.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public byte[] openSession(int programNumber, int elementaryPID)
public Session openSession(int programNumber, int elementaryPID)
throws MediaCasException {
validateInternalStates();
try {
return mICas.openSessionForStream(programNumber, elementaryPID);
return createFromSessionId(mICas.openSessionForStream(programNumber, elementaryPID));
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
@@ -453,92 +555,6 @@ public final class MediaCas {
return null;
}
/**
* Close the specified session.
*
* @param sessionId the session to be closed.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void closeSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mICas.closeSession(sessionId);
} catch (ServiceSpecificException e) {
MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
/**
* Set the private data for a session.
*
* @param sessionId the session for which the private data is intended.
* @param data byte array of the private data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data)
throws MediaCasException {
validateInternalStates();
try {
mICas.setSessionPrivateData(sessionId, data);
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
/**
* Send a received ECM packet to the specified session of the CA system.
*
* @param sessionId the session for which the ECM is intended.
* @param data byte array of the ECM data.
* @param offset position within data where the ECM data begins.
* @param length length of the data (starting from offset).
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data,
int offset, int length) throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEcm(sessionId, mCasData);
} catch (ServiceSpecificException e) {
MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
/**
* Send a received ECM packet to the specified session of the CA system.
* This is similar to {@link #processEcm(byte[], byte[], int, int)}
* except that the entire byte array is sent.
*
* @param sessionId the session for which the ECM is intended.
* @param data byte array of the ECM data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
* @throws MediaCasException for CAS-specific errors.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
throws MediaCasException {
processEcm(sessionId, data, 0, data.length);
}
/**
* Send a received EMM packet to the CA system.
*
@@ -650,10 +666,8 @@ public final class MediaCas {
}
}
/**
* Release the MediaCas instance.
*/
public void release() {
@Override
public void close() {
if (mICas != null) {
try {
mICas.release();
@@ -666,6 +680,6 @@ public final class MediaCas {
@Override
protected void finalize() {
release();
close();
}
}

View File

@@ -38,7 +38,7 @@ import java.nio.ByteBuffer;
* Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
*
*/
public final class MediaDescrambler {
public final class MediaDescrambler implements AutoCloseable {
private static final String TAG = "MediaDescrambler";
private IDescrambler mIDescrambler;
@@ -141,17 +141,17 @@ public final class MediaDescrambler {
* android.media.MediaCodec#queueSecureInputBuffer} by specifying even
* or odd key in the {@link android.media.MediaCodec.CryptoInfo#key} field.
*
* @param sessionId the MediaCas sessionId to associate with this
* @param session the MediaCas session to associate with this
* MediaDescrambler instance.
*
* @throws IllegalStateException if the descrambler instance is not valid.
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final void setMediaCasSession(@NonNull byte[] sessionId) {
public final void setMediaCasSession(@NonNull MediaCas.Session session) {
validateInternalStates();
try {
mIDescrambler.setMediaCasSession(sessionId);
mIDescrambler.setMediaCasSession(session.mSessionId);
} catch (ServiceSpecificException e) {
MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
@@ -163,11 +163,10 @@ public final class MediaDescrambler {
* Descramble a ByteBuffer of data described by a
* {@link android.media.MediaCodec.CryptoInfo} structure.
*
* @param srcBuf ByteBuffer containing the scrambled data.
* @param srcPos position within src where the scrambled data starts.
* @param dstBuf ByteBuffer to descramble into. If null, descrambling will happen
* in-place and src will be used as dst.
* @param dstPos position within dst to put the descrambled data.
* @param srcBuf ByteBuffer containing the scrambled data, which starts at
* srcBuf.position().
* @param dstBuf ByteBuffer to hold the descrambled data, which starts at
* dstBuf.position().
* @param cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
* describing the subsamples contained in src.
*
@@ -178,7 +177,7 @@ public final class MediaDescrambler {
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final int descramble(
@NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
@NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
@NonNull MediaCodec.CryptoInfo cryptoInfo) {
validateInternalStates();
@@ -208,14 +207,16 @@ public final class MediaDescrambler {
cryptoInfo.numSubSamples,
cryptoInfo.numBytesOfClearData,
cryptoInfo.numBytesOfEncryptedData,
srcBuf, srcPos, dstBuf, dstPos);
srcBuf, srcBuf.position(), srcBuf.limit(),
dstBuf, dstBuf.position(), dstBuf.limit());
} catch (ServiceSpecificException e) {
MediaCasStateException.throwExceptions(e);
}
return -1;
}
public final void release() {
@Override
public void close() {
if (mIDescrambler != null) {
try {
mIDescrambler.release();
@@ -229,7 +230,7 @@ public final class MediaDescrambler {
@Override
protected void finalize() {
release();
close();
}
private static native final void native_init();
@@ -237,7 +238,8 @@ public final class MediaDescrambler {
private native final void native_release();
private native final int native_descramble(
byte key, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
@NonNull ByteBuffer srcBuf, int srcOffset, ByteBuffer dstBuf, int dstOffset);
@NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
ByteBuffer dstBuf, int dstOffset, int dstLimit);
static {
System.loadLibrary("media_jni");

View File

@@ -259,11 +259,71 @@ final public class MediaExtractor {
* @param mediaCas the MediaCas object to use.
*/
public final void setMediaCas(@NonNull MediaCas mediaCas) {
mMediaCas = mediaCas;
nativeSetMediaCas(mediaCas.getBinder());
}
private native final void nativeSetMediaCas(@NonNull IBinder 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;
CasInfo(int systemId, @Nullable MediaCas.Session session) {
mSystemId = systemId;
mSession = session;
}
/**
* 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 {@link MediaCas.Session} associated with a track. The
* session is needed to initialize a descrambler in order to decode the
* scrambled track.
* <p>
* @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;
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);
}
return null;
}
@Override
protected void finalize() {
native_finalize();
@@ -307,31 +367,6 @@ final public class MediaExtractor {
return initDataMap.get(schemeUuid);
}
};
} else if (formatMap.containsKey("mime")
&& "video/mp2ts".equals(formatMap.get("mime"))) {
final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
new HashMap<UUID, DrmInitData.SchemeInitData>();
int numTracks = getTrackCount();
for (int i = 0; i < numTracks; ++i) {
Map<String, Object> trackFormatMap = getTrackFormatNative(i);
if (!trackFormatMap.containsKey("cas")) {
continue;
}
ByteBuffer buf = (ByteBuffer) trackFormatMap.get("cas");
buf.rewind();
final byte[] data = new byte[buf.remaining()];
buf.get(data);
initDataMap.put(new UUID(0, i), new DrmInitData.SchemeInitData("cas", data));
}
if (initDataMap.isEmpty()) {
return null;
}
return new DrmInitData() {
public SchemeInitData get(UUID schemeUuid) {
return initDataMap.get(schemeUuid);
}
};
} else {
int numTracks = getTrackCount();
for (int i = 0; i < numTracks; ++i) {
@@ -349,8 +384,8 @@ final public class MediaExtractor {
}
};
}
return null;
}
return null;
}
/**
@@ -680,5 +715,7 @@ final public class MediaExtractor {
native_init();
}
private MediaCas mMediaCas;
private long mNativeContext;
}

View File

@@ -767,6 +767,29 @@ public final class MediaFormat {
*/
public static final String KEY_TRACK_ID = "track-id";
/**
* A key describing the system id of the conditional access system used to scramble
* a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
* access system.
* <p>
* The associated value is an integer.
* @hide
*/
public static final String KEY_CA_SYSTEM_ID = "ca-system-id";
/**
* A key describing the {@link MediaCas.Session} object associated with a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
* access system.
* <p>
* The associated value is a ByteBuffer.
* @hide
*/
public static final String KEY_CA_SESSION_ID = "ca-session-id";
/* package private */ MediaFormat(Map<String, Object> map) {
mMap = map;
}

View File

@@ -54,11 +54,10 @@ static void setDescrambler(
}
static status_t getBufferAndSize(
JNIEnv *env, jobject byteBuf, jint offset, size_t length,
JNIEnv *env, jobject byteBuf, jint offset, jint limit, size_t length,
void **outPtr, jbyteArray *outByteArray) {
void *ptr = env->GetDirectBufferAddress(byteBuf);
size_t bufSize;
jbyteArray byteArray = NULL;
ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
@@ -78,13 +77,9 @@ static status_t getBufferAndSize(
jboolean isCopy;
ptr = env->GetByteArrayElements(byteArray, &isCopy);
bufSize = (size_t) env->GetArrayLength(byteArray);
} else {
bufSize = (size_t) env->GetDirectBufferCapacity(byteBuf);
}
if (length + offset > bufSize) {
if ((jint)length + offset > limit) {
if (byteArray != NULL) {
env->ReleaseByteArrayElements(byteArray, (jbyte *)ptr, 0);
}
@@ -294,7 +289,8 @@ static void throwServiceSpecificException(
static jint android_media_MediaDescrambler_native_descramble(
JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
jobject srcBuf, jint srcOffset, jobject dstBuf, jint dstOffset) {
jobject srcBuf, jint srcOffset, jint srcLimit,
jobject dstBuf, jint dstOffset, jint dstLimit) {
sp<JDescrambler> descrambler = getDescrambler(env, thiz);
if (descrambler == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
@@ -307,7 +303,7 @@ static jint android_media_MediaDescrambler_native_descramble(
numBytesOfEncryptedDataObj, &subSamples);
if (totalLength < 0) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"Invalid sub sample info!");
"Invalid subsample info!");
return -1;
}
@@ -315,16 +311,23 @@ static jint android_media_MediaDescrambler_native_descramble(
void *srcPtr = NULL, *dstPtr = NULL;
jbyteArray srcArray = NULL, dstArray = NULL;
status_t err = getBufferAndSize(
env, srcBuf, srcOffset, totalLength, &srcPtr, &srcArray);
env, srcBuf, srcOffset, srcLimit, totalLength, &srcPtr, &srcArray);
if (err == OK) {
if (dstBuf == NULL) {
dstPtr = srcPtr;
} else {
err = getBufferAndSize(
env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
env, dstBuf, dstOffset, dstLimit, totalLength, &dstPtr, &dstArray);
}
}
if (err != OK) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"Invalid buffer offset and/or size for subsamples!");
return -1;
}
Status status;
if (err == OK) {
status = descrambler->descramble(
@@ -394,7 +397,7 @@ static const JNINativeMethod gMethods[] = {
(void *)android_media_MediaDescrambler_native_init },
{ "native_setup", "(Landroid/os/IBinder;)V",
(void *)android_media_MediaDescrambler_native_setup },
{ "native_descramble", "(BI[I[ILjava/nio/ByteBuffer;ILjava/nio/ByteBuffer;I)I",
{ "native_descramble", "(BI[I[ILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)I",
(void *)android_media_MediaDescrambler_native_descramble },
};