diff --git a/api/current.txt b/api/current.txt index 7c532d4b8ffae..e0eae2c931e94 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 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); diff --git a/api/system-current.txt b/api/system-current.txt index 99a579321c791..13e2093753089 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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 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); diff --git a/api/test-current.txt b/api/test-current.txt index 570855fb00435..b8d7dc1f5244b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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 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); diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index 611fdd1d997b3..4aae3d21bf5a2 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -51,12 +51,13 @@ import android.util.Singleton; * management messages) can be distributed out-of-band, or in-band with the stream. *

* 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}. *

* 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. *

* 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. *

*

Listeners

*

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(); } } \ No newline at end of file diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java index 2dd109721ca8a..b75b7dd8b7424 100644 --- a/media/java/android/media/MediaDescrambler.java +++ b/media/java/android/media/MediaDescrambler.java @@ -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"); diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 2ed6668112ce8..a0a6a1e7ead26 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -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. + *

+ * @see MediaDescrambler#setMediaCasSession + *

+ * @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 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 initDataMap = - new HashMap(); - - int numTracks = getTrackCount(); - for (int i = 0; i < numTracks; ++i) { - Map 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; } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index e77c00b17365d..ed5f7d8486634 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -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. + *

+ * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional + * access system. + *

+ * 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. + *

+ * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional + * access system. + *

+ * The associated value is a ByteBuffer. + * @hide + */ + public static final String KEY_CA_SESSION_ID = "ca-session-id"; + /* package private */ MediaFormat(Map map) { mMap = map; } diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp index f031dbb273c6b..85d33b7730079 100644 --- a/media/jni/android_media_MediaDescrambler.cpp +++ b/media/jni/android_media_MediaDescrambler.cpp @@ -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 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 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 }, };