OSDN Git Service

MediaCas: add framework APIs
authorChong Zhang <chz@google.com>
Tue, 3 Jan 2017 19:07:18 +0000 (11:07 -0800)
committerChong Zhang <chz@google.com>
Thu, 23 Feb 2017 01:27:01 +0000 (17:27 -0800)
Java API changes to support MediaCas framework.

Also add partial-frame related enums to MediaCodec,
MediaCodecInfo, and MediaExtractor as a way to indicate
a input frame contains partial data.

Test: basic test app and test plugin to execise the code path;
impelenting playback of CAS-secured MPEG2TS streams, including
a Widevine-based CAS plugin, locally modified ExoPlayer and
Android TV Tuner input.

bug: 22804304
Change-Id: Ia4ba1d4046f7588e42bfb21e726390457d2fc8e3

26 files changed:
Android.mk
api/current.txt
api/system-current.txt
api/test-current.txt
media/java/android/media/ICas.aidl [new file with mode: 0644]
media/java/android/media/ICasListener.aidl [new file with mode: 0644]
media/java/android/media/IDescrambler.aidl [new file with mode: 0644]
media/java/android/media/IMediaCasService.aidl [new file with mode: 0644]
media/java/android/media/MediaCas.aidl [new file with mode: 0644]
media/java/android/media/MediaCas.java [new file with mode: 0644]
media/java/android/media/MediaCasException.java [new file with mode: 0644]
media/java/android/media/MediaCodec.java
media/java/android/media/MediaCodecInfo.java
media/java/android/media/MediaDescrambler.aidl [new file with mode: 0644]
media/java/android/media/MediaDescrambler.java [new file with mode: 0644]
media/java/android/media/MediaExtractor.java
media/java/android/media/MediaFormat.java
media/java/android/media/UnsupportedCasException.java [new file with mode: 0644]
media/jni/Android.mk
media/jni/android_media_MediaCodec.cpp
media/jni/android_media_MediaCodec.h
media/jni/android_media_MediaDescrambler.cpp [new file with mode: 0644]
media/jni/android_media_MediaDescrambler.h [new file with mode: 0644]
media/jni/android_media_MediaExtractor.cpp
media/jni/android_media_MediaExtractor.h
media/jni/android_media_MediaPlayer.cpp

index b98d3bc..0cf74b8 100644 (file)
@@ -409,6 +409,10 @@ LOCAL_SRC_FILES += \
        location/java/android/location/INetInitiatedListener.aidl \
        location/java/com/android/internal/location/ILocationProvider.aidl \
        media/java/android/media/IAudioService.aidl \
+       media/java/android/media/ICas.aidl \
+       media/java/android/media/ICasListener.aidl \
+       media/java/android/media/IDescrambler.aidl \
+       media/java/android/media/IMediaCasService.aidl \
        media/java/android/media/IAudioFocusDispatcher.aidl \
        media/java/android/media/IAudioRoutesObserver.aidl \
        media/java/android/media/IMediaHTTPConnection.aidl \
index 202ea21..c41dff7 100644 (file)
@@ -21467,8 +21467,42 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
+  public final class MediaCas {
+    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    method public void closeSession(byte[]);
+    method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
+    method public static boolean isSystemIdSupported(int);
+    method public byte[] openSession(int);
+    method public byte[] openSession(int, int);
+    method public void processEcm(byte[], byte[], int, int);
+    method public void processEcm(byte[], byte[]);
+    method public void processEmm(byte[], int, int);
+    method public void processEmm(byte[]);
+    method public void provision(java.lang.String);
+    method public void refreshEntitlements(int, byte[]);
+    method public void release();
+    method public void sendEvent(int, int, byte[]);
+    method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
+    method public void setPrivateData(byte[]);
+    method public void setSessionPrivateData(byte[], byte[]);
+  }
+
+  public static abstract interface MediaCas.EventListener {
+    method public abstract void onEvent(android.media.MediaCas, int, int, byte[]);
+  }
+
+  public static class MediaCas.PluginDescriptor {
+    method public java.lang.String getName();
+    method public int getSystemId();
+  }
+
+  public class MediaCasException extends java.lang.Exception {
+    ctor public MediaCasException(java.lang.String);
+  }
+
   public final class MediaCodec {
     method public void configure(android.media.MediaFormat, android.view.Surface, android.media.MediaCrypto, int);
+    method public void configure(android.media.MediaFormat, android.view.Surface, int, android.media.MediaDescrambler);
     method public static android.media.MediaCodec createByCodecName(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createDecoderByType(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createEncoderByType(java.lang.String) throws java.io.IOException;
@@ -21508,6 +21542,7 @@ package android.media {
     field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
     field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
     field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
+    field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8
     field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
     field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
@@ -21664,6 +21699,7 @@ package android.media {
     field public static final deprecated int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback";
     field public static final java.lang.String FEATURE_IntraRefresh = "intra-refresh";
+    field public static final java.lang.String FEATURE_PartialFrame = "partial-frame";
     field public static final java.lang.String FEATURE_SecurePlayback = "secure-playback";
     field public static final java.lang.String FEATURE_TunneledPlayback = "tunneled-playback";
     field public int[] colorFormats;
@@ -21888,6 +21924,14 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
+  public final class MediaDescrambler {
+    ctor public MediaDescrambler(int) throws android.media.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 final boolean requiresSecureDecoderComponent(java.lang.String);
+    method public final void setMediaCasSession(byte[]);
+  }
+
   public class MediaDescription implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.CharSequence getDescription();
@@ -22044,8 +22088,10 @@ package android.media {
     method public final void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public final void setDataSource(java.io.FileDescriptor) throws java.io.IOException;
     method public final void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException;
+    method public final void setMediaCas(android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
+    field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
     field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
     field public static final int SEEK_TO_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_TO_NEXT_SYNC = 1; // 0x1
@@ -22142,6 +22188,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_AUDIO_OPUS = "audio/opus";
     field public static final java.lang.String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
+    field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -22152,6 +22199,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
     field public static final java.lang.String MIMETYPE_VIDEO_RAW = "video/raw";
+    field public static final java.lang.String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
     field public static final java.lang.String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     field public static final java.lang.String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
   }
@@ -23123,6 +23171,10 @@ package android.media {
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
+  public final class UnsupportedCasException extends android.media.MediaCasException {
+    ctor public UnsupportedCasException(java.lang.String);
+  }
+
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
index 2f67204..1492261 100644 (file)
@@ -23155,8 +23155,42 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
+  public final class MediaCas {
+    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    method public void closeSession(byte[]);
+    method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
+    method public static boolean isSystemIdSupported(int);
+    method public byte[] openSession(int);
+    method public byte[] openSession(int, int);
+    method public void processEcm(byte[], byte[], int, int);
+    method public void processEcm(byte[], byte[]);
+    method public void processEmm(byte[], int, int);
+    method public void processEmm(byte[]);
+    method public void provision(java.lang.String);
+    method public void refreshEntitlements(int, byte[]);
+    method public void release();
+    method public void sendEvent(int, int, byte[]);
+    method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
+    method public void setPrivateData(byte[]);
+    method public void setSessionPrivateData(byte[], byte[]);
+  }
+
+  public static abstract interface MediaCas.EventListener {
+    method public abstract void onEvent(android.media.MediaCas, int, int, byte[]);
+  }
+
+  public static class MediaCas.PluginDescriptor {
+    method public java.lang.String getName();
+    method public int getSystemId();
+  }
+
+  public class MediaCasException extends java.lang.Exception {
+    ctor public MediaCasException(java.lang.String);
+  }
+
   public final class MediaCodec {
     method public void configure(android.media.MediaFormat, android.view.Surface, android.media.MediaCrypto, int);
+    method public void configure(android.media.MediaFormat, android.view.Surface, int, android.media.MediaDescrambler);
     method public static android.media.MediaCodec createByCodecName(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createDecoderByType(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createEncoderByType(java.lang.String) throws java.io.IOException;
@@ -23196,6 +23230,7 @@ package android.media {
     field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
     field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
     field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
+    field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8
     field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
     field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
@@ -23352,6 +23387,7 @@ package android.media {
     field public static final deprecated int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback";
     field public static final java.lang.String FEATURE_IntraRefresh = "intra-refresh";
+    field public static final java.lang.String FEATURE_PartialFrame = "partial-frame";
     field public static final java.lang.String FEATURE_SecurePlayback = "secure-playback";
     field public static final java.lang.String FEATURE_TunneledPlayback = "tunneled-playback";
     field public int[] colorFormats;
@@ -23576,6 +23612,14 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
+  public final class MediaDescrambler {
+    ctor public MediaDescrambler(int) throws android.media.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 final boolean requiresSecureDecoderComponent(java.lang.String);
+    method public final void setMediaCasSession(byte[]);
+  }
+
   public class MediaDescription implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.CharSequence getDescription();
@@ -23732,8 +23776,10 @@ package android.media {
     method public final void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public final void setDataSource(java.io.FileDescriptor) throws java.io.IOException;
     method public final void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException;
+    method public final void setMediaCas(android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
+    field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
     field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
     field public static final int SEEK_TO_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_TO_NEXT_SYNC = 1; // 0x1
@@ -23830,6 +23876,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_AUDIO_OPUS = "audio/opus";
     field public static final java.lang.String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
+    field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -23840,6 +23887,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
     field public static final java.lang.String MIMETYPE_VIDEO_RAW = "video/raw";
+    field public static final java.lang.String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
     field public static final java.lang.String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     field public static final java.lang.String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
   }
@@ -24822,6 +24870,10 @@ package android.media {
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
+  public final class UnsupportedCasException extends android.media.MediaCasException {
+    ctor public UnsupportedCasException(java.lang.String);
+  }
+
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
index 41f279b..d52b780 100644 (file)
@@ -21563,8 +21563,42 @@ package android.media {
     field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
   }
 
+  public final class MediaCas {
+    ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+    method public void closeSession(byte[]);
+    method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
+    method public static boolean isSystemIdSupported(int);
+    method public byte[] openSession(int);
+    method public byte[] openSession(int, int);
+    method public void processEcm(byte[], byte[], int, int);
+    method public void processEcm(byte[], byte[]);
+    method public void processEmm(byte[], int, int);
+    method public void processEmm(byte[]);
+    method public void provision(java.lang.String);
+    method public void refreshEntitlements(int, byte[]);
+    method public void release();
+    method public void sendEvent(int, int, byte[]);
+    method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
+    method public void setPrivateData(byte[]);
+    method public void setSessionPrivateData(byte[], byte[]);
+  }
+
+  public static abstract interface MediaCas.EventListener {
+    method public abstract void onEvent(android.media.MediaCas, int, int, byte[]);
+  }
+
+  public static class MediaCas.PluginDescriptor {
+    method public java.lang.String getName();
+    method public int getSystemId();
+  }
+
+  public class MediaCasException extends java.lang.Exception {
+    ctor public MediaCasException(java.lang.String);
+  }
+
   public final class MediaCodec {
     method public void configure(android.media.MediaFormat, android.view.Surface, android.media.MediaCrypto, int);
+    method public void configure(android.media.MediaFormat, android.view.Surface, int, android.media.MediaDescrambler);
     method public static android.media.MediaCodec createByCodecName(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createDecoderByType(java.lang.String) throws java.io.IOException;
     method public static android.media.MediaCodec createEncoderByType(java.lang.String) throws java.io.IOException;
@@ -21604,6 +21638,7 @@ package android.media {
     field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
     field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
     field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
+    field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8
     field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
     field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
     field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
@@ -21760,6 +21795,7 @@ package android.media {
     field public static final deprecated int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback";
     field public static final java.lang.String FEATURE_IntraRefresh = "intra-refresh";
+    field public static final java.lang.String FEATURE_PartialFrame = "partial-frame";
     field public static final java.lang.String FEATURE_SecurePlayback = "secure-playback";
     field public static final java.lang.String FEATURE_TunneledPlayback = "tunneled-playback";
     field public int[] colorFormats;
@@ -21984,6 +22020,14 @@ package android.media {
     method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
   }
 
+  public final class MediaDescrambler {
+    ctor public MediaDescrambler(int) throws android.media.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 final boolean requiresSecureDecoderComponent(java.lang.String);
+    method public final void setMediaCasSession(byte[]);
+  }
+
   public class MediaDescription implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.CharSequence getDescription();
@@ -22140,8 +22184,10 @@ package android.media {
     method public final void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public final void setDataSource(java.io.FileDescriptor) throws java.io.IOException;
     method public final void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException;
+    method public final void setMediaCas(android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
+    field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
     field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
     field public static final int SEEK_TO_CLOSEST_SYNC = 2; // 0x2
     field public static final int SEEK_TO_NEXT_SYNC = 1; // 0x1
@@ -22238,6 +22284,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_AUDIO_OPUS = "audio/opus";
     field public static final java.lang.String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
+    field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -22248,6 +22295,7 @@ package android.media {
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
     field public static final java.lang.String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
     field public static final java.lang.String MIMETYPE_VIDEO_RAW = "video/raw";
+    field public static final java.lang.String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
     field public static final java.lang.String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     field public static final java.lang.String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
   }
@@ -23219,6 +23267,10 @@ package android.media {
     field public static final int TONE_SUP_RINGTONE = 23; // 0x17
   }
 
+  public final class UnsupportedCasException extends android.media.MediaCasException {
+    ctor public UnsupportedCasException(java.lang.String);
+  }
+
   public final class UnsupportedSchemeException extends android.media.MediaDrmException {
     ctor public UnsupportedSchemeException(java.lang.String);
   }
diff --git a/media/java/android/media/ICas.aidl b/media/java/android/media/ICas.aidl
new file mode 100644 (file)
index 0000000..6b2ce4a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.media.MediaCas;
+
+/** @hide */
+interface ICas {
+    void setPrivateData(in byte[] pvtData);
+    byte[] openSession(int program_number);
+    byte[] openSessionForStream(int program_number, int elementary_PID);
+    void closeSession(in byte[] sessionId);
+    void setSessionPrivateData(in byte[] sessionId, in byte[] pvtData);
+    void processEcm(in byte[] sessionId, in MediaCas.ParcelableCasData ecm);
+    void processEmm(in MediaCas.ParcelableCasData emm);
+    void sendEvent(int event, int arg, in @nullable byte[] eventData);
+    void provision(String provisionString);
+    void refreshEntitlements(int refreshType, in @nullable byte[] refreshData);
+    void release();
+}
\ No newline at end of file
diff --git a/media/java/android/media/ICasListener.aidl b/media/java/android/media/ICasListener.aidl
new file mode 100644 (file)
index 0000000..01a5abc
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+interface ICasListener {
+    void onEvent(int event, int arg, in @nullable byte[] data);
+}
\ No newline at end of file
diff --git a/media/java/android/media/IDescrambler.aidl b/media/java/android/media/IDescrambler.aidl
new file mode 100644 (file)
index 0000000..fdf99eb
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.media.MediaDescrambler;
+
+/** @hide */
+interface IDescrambler {
+    void setMediaCasSession(in byte[] sessionId);
+    boolean requiresSecureDecoderComponent(String mime);
+    int descramble(in MediaDescrambler.DescrambleInfo descrambleInfo);
+    void release();
+}
\ No newline at end of file
diff --git a/media/java/android/media/IMediaCasService.aidl b/media/java/android/media/IMediaCasService.aidl
new file mode 100644 (file)
index 0000000..44f6825
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.media.IDescrambler;
+import android.media.ICas;
+import android.media.ICasListener;
+import android.media.MediaCas;
+
+/** @hide */
+interface IMediaCasService {
+    MediaCas.ParcelableCasPluginDescriptor[] enumeratePlugins();
+    boolean isSystemIdSupported(int CA_system_id);
+    ICas createPlugin(int CA_system_id, ICasListener listener);
+    boolean isDescramblerSupported(int CA_system_id);
+    IDescrambler createDescrambler(int CA_system_id);
+}
+
diff --git a/media/java/android/media/MediaCas.aidl b/media/java/android/media/MediaCas.aidl
new file mode 100644 (file)
index 0000000..cb8d0c6
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable MediaCas.ParcelableCasPluginDescriptor cpp_header "media/MediaCasDefs.h";
+
+/** @hide */
+parcelable MediaCas.ParcelableCasData cpp_header "media/MediaCasDefs.h";
\ No newline at end of file
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
new file mode 100644 (file)
index 0000000..2e22132
--- /dev/null
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2017 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.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Singleton;
+
+/**
+ * MediaCas can be used to obtain keys for descrambling protected media streams, in
+ * conjunction with {@link android.media.MediaDescrambler}. The MediaCas APIs are
+ * designed to support conditional access such as those in the ISO/IEC13818-1.
+ * The CA system is identified by a 16-bit integer CA_system_id. The scrambling
+ * algorithms are usually proprietary and implemented by vendor-specific CA plugins
+ * installed on the device.
+ * <p>
+ * The app is responsible for constructing a MediaCas object for the CA system it
+ * intends to use. The app can query if a certain CA system is supported using static
+ * method {@link #isSystemIdSupported}. It can also obtain the entire list of supported
+ * CA systems using static method {@link #enumeratePlugins}.
+ * <p>
+ * Once the MediaCas object is constructed, the app should properly provision it by
+ * using method {@link #provision} and/or {@link #processEmm}. The EMMs (Entitlement
+ * 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}.
+ * <p>
+ * The app next constructs a MediaDescrambler object, and initializes it with the
+ * sessionId 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}.
+ * <p>
+ * If the app handles sample extraction using its own extractor, it can use
+ * MediaDescrambler to descramble samples into clear buffers (if the session's license
+ * doesn't require secure decoders), or descramble a small amount of data to retrieve
+ * information necessary for the downstream pipeline to process the sample (if the
+ * session's license requires secure decoders).
+ * <p>
+ * If the session requires a secure decoder, a MediaDescrambler needs to be provided to
+ * MediaCodec to descramble samples queued by {@link MediaCodec#queueSecureInputBuffer}
+ * into protected buffers. The app should use {@link MediaCodec#configure(MediaFormat,
+ * android.view.Surface, int, MediaDescrambler)} instead of the normal {@link
+ * MediaCodec#configure(MediaFormat, android.view.Surface, MediaCrypto, int)} method
+ * to configure MediaCodec.
+ * <p>
+ * <h3>Using Android's MediaExtractor</h3>
+ * <p>
+ * 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.
+ * <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.
+ * <p>
+ * TODO: determine exception handling schemes.
+ * <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 {
+    private static final String TAG = "MediaCas";
+    private final ParcelableCasData mCasData = new ParcelableCasData();
+    private ICas mICas;
+    private EventListener mListener;
+    private HandlerThread mHandlerThread;
+    private EventHandler mEventHandler;
+
+    private static final Singleton<IMediaCasService> gDefault =
+            new Singleton<IMediaCasService>() {
+        @Override
+        protected IMediaCasService create() {
+            return IMediaCasService.Stub.asInterface(
+                    ServiceManager.getService("media.cas"));
+        }
+    };
+
+    static IMediaCasService getService() {
+        return gDefault.get();
+    }
+
+    private void validateInternalStates() {
+        if (mICas == null) {
+            throw new IllegalStateException();
+        }
+    }
+
+    private void cleanupAndRethrowIllegalState() {
+        mICas = null;
+        throw new IllegalStateException();
+    }
+
+    private class EventHandler extends Handler
+    {
+        private static final int MSG_CAS_EVENT = 0;
+
+        public EventHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_CAS_EVENT) {
+                mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, (byte[]) msg.obj);
+            }
+        }
+    }
+
+    private final ICasListener.Stub mBinder = new ICasListener.Stub() {
+        @Override
+        public void onEvent(int event, int arg, @Nullable byte[] data)
+                throws RemoteException {
+            mEventHandler.sendMessage(mEventHandler.obtainMessage(
+                    EventHandler.MSG_CAS_EVENT, event, arg, data));
+        }
+    };
+
+    /**
+     * Class for parceling byte array data over ICas binder.
+     */
+    static class ParcelableCasData implements Parcelable {
+        private byte[] mData;
+        private int mOffset;
+        private int mLength;
+
+        ParcelableCasData() {
+            mData = null;
+            mOffset = mLength = 0;
+        }
+
+        private ParcelableCasData(Parcel in) {
+            this();
+        }
+
+        void set(@NonNull byte[] data, int offset, int length) {
+            mData = data;
+            mOffset = offset;
+            mLength = length;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeByteArray(mData, mOffset, mLength);
+        }
+
+        public static final Parcelable.Creator<ParcelableCasData> CREATOR
+                = new Parcelable.Creator<ParcelableCasData>() {
+            public ParcelableCasData createFromParcel(Parcel in) {
+                return new ParcelableCasData(in);
+            }
+
+            public ParcelableCasData[] newArray(int size) {
+                return new ParcelableCasData[size];
+            }
+        };
+    }
+
+    /**
+     * Describe a CAS plugin with its CA_system_ID and string name.
+     *
+     * Returned as results of {@link #enumeratePlugins}.
+     *
+     */
+    public static class PluginDescriptor {
+        private final int mCASystemId;
+        private final String mName;
+
+        private PluginDescriptor() {
+            mCASystemId = 0xffff;
+            mName = null;
+        }
+
+        PluginDescriptor(int CA_system_id, String name) {
+            mCASystemId = CA_system_id;
+            mName = name;
+        }
+
+        public int getSystemId() {
+            return mCASystemId;
+        }
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "PluginDescriptor {" + mCASystemId + ", " + mName + "}";
+        }
+    }
+
+    /**
+     * Class for parceling CAS plugin descriptors over IMediaCasService binder.
+     */
+    static class ParcelableCasPluginDescriptor
+        extends PluginDescriptor implements Parcelable {
+
+        private ParcelableCasPluginDescriptor(int CA_system_id, String name) {
+            super(CA_system_id, name);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            Log.w(TAG, "ParcelableCasPluginDescriptor.writeToParcel shouldn't be called!");
+        }
+
+        public static final Parcelable.Creator<ParcelableCasPluginDescriptor> CREATOR
+                = new Parcelable.Creator<ParcelableCasPluginDescriptor>() {
+            public ParcelableCasPluginDescriptor createFromParcel(Parcel in) {
+                int CA_system_id = in.readInt();
+                String name = in.readString();
+                return new ParcelableCasPluginDescriptor(CA_system_id, name);
+            }
+
+            public ParcelableCasPluginDescriptor[] newArray(int size) {
+                return new ParcelableCasPluginDescriptor[size];
+            }
+        };
+    }
+
+    /**
+     * Query if a certain CA system is supported on this device.
+     *
+     * @param CA_system_id the id of the CA system.
+     *
+     * @return Whether the specified CA system is supported on this device.
+     */
+    public static boolean isSystemIdSupported(int CA_system_id) {
+        IMediaCasService service = getService();
+
+        if (service != null) {
+            try {
+                return service.isSystemIdSupported(CA_system_id);
+            } catch (RemoteException e) {
+            }
+        }
+        return false;
+    }
+
+    /**
+     * List all available CA plugins on the device.
+     *
+     * @return an array of descriptors for the available CA plugins.
+     */
+    public static PluginDescriptor[] enumeratePlugins() {
+        IMediaCasService service = getService();
+
+        if (service != null) {
+            try {
+                ParcelableCasPluginDescriptor[] descriptors = service.enumeratePlugins();
+                if (descriptors.length == 0) {
+                    return null;
+                }
+                PluginDescriptor[] results = new PluginDescriptor[descriptors.length];
+                for (int i = 0; i < results.length; i++) {
+                    results[i] = descriptors[i];
+                }
+                return results;
+            } catch (RemoteException e) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Instantiate a CA system of the specified system id.
+     *
+     * @param CA_system_id The system id of the CA system.
+     *
+     * @throws UnsupportedCasException if the device does not support the
+     * specified CA system.
+     */
+    public MediaCas(int CA_system_id) throws UnsupportedCasException {
+        try {
+            mICas = getService().createPlugin(CA_system_id, mBinder);
+        } catch(Exception e) {
+            Log.e(TAG, "Failed to create plugin: " + e);
+            mICas = null;
+        } finally {
+            if (mICas == null) {
+                throw new UnsupportedCasException(
+                        "Unsupported CA_system_id " + CA_system_id);
+            }
+        }
+    }
+
+    IBinder getBinder() {
+        validateInternalStates();
+
+        return mICas.asBinder();
+    }
+
+    /**
+     * An interface registered by the caller to {@link #setEventListener}
+     * to receives scheme-specific notifications from a MediaCas instance.
+     */
+    public interface EventListener {
+        /**
+         * Notify the listener of a scheme-specific event from the CA system.
+         *
+         * @param MediaCas the MediaCas object to receive this event.
+         * @param event an integer whose meaning is scheme-specific.
+         * @param arg an integer whose meaning is scheme-specific.
+         * @param data a byte array of data whose format and meaning are
+         * scheme-specific.
+         */
+        void onEvent(MediaCas MediaCas, int event, int arg, @Nullable byte[] data);
+    }
+
+    /**
+     * Set an event listener to receive notifications from the MediaCas instance.
+     *
+     * @param listener the event listener to be set.
+     * @param handler the handler whose looper the event listener will be called on.
+     * If handler is null, we'll try to use current thread's looper, or the main
+     * looper. If neither are available, an internal thread will be created instead.
+     */
+    public void setEventListener(
+            @Nullable EventListener listener, @Nullable Handler handler) {
+        mListener = listener;
+
+        if (mListener == null) {
+            mEventHandler = null;
+            return;
+        }
+
+        Looper looper = (handler != null) ? handler.getLooper() : null;
+        if (looper == null
+                && (looper = Looper.myLooper()) == null
+                && (looper = Looper.getMainLooper()) == null) {
+            if (mHandlerThread == null || !mHandlerThread.isAlive()) {
+                mHandlerThread = new HandlerThread("MediaCasEventThread",
+                        Process.THREAD_PRIORITY_FOREGROUND);
+                mHandlerThread.start();
+            }
+            looper = mHandlerThread.getLooper();
+        }
+        mEventHandler = new EventHandler(looper);
+    }
+
+    /*
+     * TODO: handle ServiceSpecificException from the IMediaCas
+     * All Drm-specific failures will be thrown by mICas as
+     * ServiceSpecificException exception with Drm error code.
+     * These need to be re-thrown as crypto exceptions.
+     */
+
+    /**
+     * Send the private data for the CA system.
+     *
+     * @param data byte array of the private data.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void setPrivateData(@NonNull byte[] data) {
+        validateInternalStates();
+
+        try {
+            mICas.setPrivateData(data);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * Open a session for the specified program.
+     *
+     * @param programNumber program_number of the program (as in ISO/IEC13818-1).
+     *
+     * @return session id of the newly opened session.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid,
+     * or IllegalArgumentException if a session for the program already exists.
+     */
+    public byte[] openSession(int programNumber) {
+        validateInternalStates();
+
+        try {
+            return mICas.openSession(programNumber);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+        return null;
+    }
+
+    /**
+     * Open a session for the specified stream.
+     *
+     * @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.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid,
+     * or IllegalArgumentException if a session for the stream already exists.
+     */
+    public byte[] openSession(int programNumber, int elementaryPID) {
+        validateInternalStates();
+
+        try {
+            return mICas.openSessionForStream(programNumber, elementaryPID);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+        return null;
+    }
+
+    /**
+     * Close the specified session.
+     *
+     * @param sessionId the session to be closed.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid,
+     * or IllegalArgumentException if the session is not valid.
+     */
+    public void closeSession(@NonNull byte[] sessionId) {
+        validateInternalStates();
+
+        try {
+            mICas.closeSession(sessionId);
+        } 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,
+     * or IllegalArgumentException if the session is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data) {
+        validateInternalStates();
+
+        try {
+            mICas.setSessionPrivateData(sessionId, data);
+        } 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,
+     * or IllegalArgumentException if the session is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void processEcm(
+            @NonNull byte[] sessionId, @NonNull byte[] data, int offset, int length) {
+        validateInternalStates();
+
+        try {
+            mCasData.set(data, offset, length);
+            mICas.processEcm(sessionId, mCasData);
+        } 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,
+     * or IllegalArgumentException if the session is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data) {
+        processEcm(sessionId, data, 0, data.length);
+    }
+
+    /**
+     * Send a received EMM packet to the CA system.
+     *
+     * @param data byte array of the EMM data.
+     * @param offset position within data where the EMM data begins.
+     * @param length length of the data (starting from offset).
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void processEmm(@NonNull byte[] data, int offset, int length) {
+        validateInternalStates();
+
+        try {
+            mCasData.set(data, offset, length);
+            mICas.processEmm(mCasData);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * Send a received EMM packet to the CA system. This is similar to
+     * {@link #processEmm(byte[], int, int)} except that the entire byte
+     * array is sent.
+     *
+     * @param data byte array of the EMM data.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    /*
+     * TODO: need to re-throw DRM-specific exceptions
+     */
+    public void processEmm(@NonNull byte[] data) {
+        processEmm(data, 0, data.length);
+    }
+
+    /**
+     * Send an event to a CA system. The format of the event is scheme-specific
+     * and is opaque to the framework.
+     *
+     * @param event an integer denoting a scheme-specific event to be sent.
+     * @param arg a scheme-specific integer argument for the event.
+     * @param data a byte array containing scheme-specific data for the event.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    public void sendEvent(int event, int arg, @Nullable byte[] data) {
+        validateInternalStates();
+
+        try {
+            mICas.sendEvent(event, arg, data);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * Initiate a provisioning operation for a CA system.
+     *
+     * @param provisionString string containing information needed for the
+     * provisioning operation, the format of which is scheme and implementation
+     * specific.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    public void provision(@NonNull String provisionString) {
+        validateInternalStates();
+
+        try {
+            mICas.provision(provisionString);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * Notify the CA system to refresh entitlement keys.
+     *
+     * @param refreshType the type of the refreshment.
+     * @param refreshData private data associated with the refreshment.
+     *
+     * @throws IllegalStateException if the MediaCas instance is not valid.
+     */
+    /*
+     * TODO: define enums for refreshType
+     */
+    public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) {
+        validateInternalStates();
+
+        try {
+            mICas.refreshEntitlements(refreshType, refreshData);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * Release the MediaCas instance.
+     */
+    public void release() {
+        if (mICas != null) {
+            try {
+                mICas.release();
+            } catch (RemoteException e) {
+            } finally {
+                mICas = null;
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        release();
+    }
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
new file mode 100644 (file)
index 0000000..1d5d3cd
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Base class for MediaCas exceptions
+ */
+public class MediaCasException extends Exception {
+    public MediaCasException(String detailMessage) {
+        super(detailMessage);
+    }
+}
index 75ccffe..1ca9658 100644 (file)
@@ -25,6 +25,7 @@ import android.graphics.SurfaceTexture;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.view.Surface;
@@ -1567,6 +1568,13 @@ final public class MediaCodec {
      */
     public static final int BUFFER_FLAG_END_OF_STREAM = 4;
 
+    /**
+     * 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.
+     */
+    public static final int BUFFER_FLAG_PARTIAL_FRAME = 8;
+
     /** @hide */
     @IntDef(
         flag = true,
@@ -1575,6 +1583,7 @@ final public class MediaCodec {
             BUFFER_FLAG_KEY_FRAME,
             BUFFER_FLAG_CODEC_CONFIG,
             BUFFER_FLAG_END_OF_STREAM,
+            BUFFER_FLAG_PARTIAL_FRAME,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BufferFlag {}
@@ -1851,6 +1860,48 @@ final public class MediaCodec {
             @Nullable MediaFormat format,
             @Nullable Surface surface, @Nullable MediaCrypto crypto,
             @ConfigureFlag int flags) {
+        configure(format, surface, crypto, null, flags);
+    }
+
+    /**
+     * Configure a component to be used with a descrambler.
+     * @param format The format of the input data (decoder) or the desired
+     *               format of the output data (encoder). Passing {@code null}
+     *               as {@code format} is equivalent to passing an
+     *               {@link MediaFormat#MediaFormat an empty mediaformat}.
+     * @param surface Specify a surface on which to render the output of this
+     *                decoder. Pass {@code null} as {@code surface} if the
+     *                codec does not generate raw video output (e.g. not a video
+     *                decoder) and/or if you want to configure the codec for
+     *                {@link ByteBuffer} output.
+     * @param flags   Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
+     *                component as an encoder.
+     * @param descrambler Specify a descrambler object to facilitate secure
+     *                descrambling of the media data. descrambler must not be
+     *                null if this method is used. For non-secure codecs, use
+     *                {@link #configure} and with null crypto parameter.
+     * @throws IllegalArgumentException if the surface has been released (or is invalid),
+     * or the format is unacceptable (e.g. missing a mandatory key),
+     * or the flags are not set properly
+     * (e.g. missing {@link #CONFIGURE_FLAG_ENCODE} for an encoder).
+     * @throws IllegalStateException if not in the Uninitialized state.
+     * @throws CryptoException upon DRM error.
+     * @throws CodecException upon codec error.
+     */
+    public void configure(
+            @Nullable MediaFormat format, @Nullable Surface surface,
+            @ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
+        configure(format, surface, null, descrambler.getBinder(), flags);
+    }
+
+    private void configure(
+            @Nullable MediaFormat format, @Nullable Surface surface,
+            @Nullable MediaCrypto crypto, @Nullable IBinder descramblerBinder,
+            @ConfigureFlag int flags) {
+        if (crypto != null && descramblerBinder != null) {
+            throw new IllegalArgumentException("Can't use crypto and descrambler together!");
+        }
+
         String[] keys = null;
         Object[] values = null;
 
@@ -1881,7 +1932,7 @@ final public class MediaCodec {
 
         mHasSurface = surface != null;
 
-        native_configure(keys, values, surface, crypto, flags);
+        native_configure(keys, values, surface, crypto, descramblerBinder, flags);
     }
 
     /**
@@ -1959,7 +2010,8 @@ final public class MediaCodec {
 
     private native final void native_configure(
             @Nullable String[] keys, @Nullable Object[] values,
-            @Nullable Surface surface, @Nullable MediaCrypto crypto, @ConfigureFlag int flags);
+            @Nullable Surface surface, @Nullable MediaCrypto crypto,
+            @Nullable IBinder descramblerBinder, @ConfigureFlag int flags);
 
     /**
      * Requests a Surface to use as the input to an encoder, in place of input buffers.  This
index 8ada295..3ac4c34 100644 (file)
@@ -460,6 +460,11 @@ public final class MediaCodecInfo {
         public static final String FEATURE_TunneledPlayback       = "tunneled-playback";
 
         /**
+         * <b>video decoder only</b>: codec supports queuing partial frames.
+         */
+        public static final String FEATURE_PartialFrame = "partial-frame";
+
+        /**
          * <b>video encoder only</b>: codec supports intra refresh.
          */
         public static final String FEATURE_IntraRefresh = "intra-refresh";
@@ -489,6 +494,7 @@ public final class MediaCodecInfo {
             new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
             new Feature(FEATURE_SecurePlayback,   (1 << 1), false),
             new Feature(FEATURE_TunneledPlayback, (1 << 2), false),
+            new Feature(FEATURE_PartialFrame,     (1 << 3), false),
         };
 
         private static final Feature[] encoderFeatures = {
diff --git a/media/java/android/media/MediaDescrambler.aidl b/media/java/android/media/MediaDescrambler.aidl
new file mode 100644 (file)
index 0000000..e789244
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable MediaDescrambler.DescrambleInfo cpp_header "media/MediaCasDefs.h";
\ No newline at end of file
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
new file mode 100644 (file)
index 0000000..f5eede8
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 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.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+/**
+ * MediaDescrambler class can be used in conjunction with {@link android.media.MediaCodec}
+ * and {@link android.media.MediaExtractor} to decode media data scrambled by conditional
+ * access (CA) systems such as those in the ISO/IEC13818-1.
+ *
+ * A MediaDescrambler object is initialized from a session opened by a MediaCas object,
+ * and can be used to descramble media streams scrambled with that session's keys.
+ *
+ * Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
+ *
+ */
+public final class MediaDescrambler {
+    private static final String TAG = "MediaDescrambler";
+    private IDescrambler mIDescrambler;
+
+    private final void validateInternalStates() {
+        if (mIDescrambler == null) {
+            throw new IllegalStateException();
+        }
+    }
+
+    private final void cleanupAndRethrowIllegalState() {
+        mIDescrambler = null;
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Class for parceling descrambling parameters over IDescrambler binder.
+     */
+    static class DescrambleInfo implements Parcelable {
+        private DescrambleInfo() {
+            // TODO: implement
+        }
+
+        private DescrambleInfo(Parcel in) {
+            // TODO: disable
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            // TODO: implement
+        }
+
+        public static final Parcelable.Creator<DescrambleInfo> CREATOR
+                = new Parcelable.Creator<DescrambleInfo>() {
+            public DescrambleInfo createFromParcel(Parcel in) {
+                return new DescrambleInfo(in);
+            }
+
+            public DescrambleInfo[] newArray(int size) {
+                return new DescrambleInfo[size];
+            }
+        };
+    }
+
+    /**
+     * Instantiate a MediaDescrambler.
+     *
+     * @param CA_system_id The system id of the scrambling scheme.
+     *
+     * @throws UnsupportedCasException if the scrambling scheme is not supported.
+     */
+    public MediaDescrambler(int CA_system_id) throws UnsupportedCasException {
+        try {
+            mIDescrambler = MediaCas.getService().createDescrambler(CA_system_id);
+        } catch(Exception e) {
+            Log.e(TAG, "Failed to create descrambler: " + e);
+            mIDescrambler = null;
+        } finally {
+            if (mIDescrambler == null) {
+                throw new UnsupportedCasException("Unsupported CA_system_id " + CA_system_id);
+            }
+        }
+        native_setup(mIDescrambler.asBinder());
+    }
+
+    IBinder getBinder() {
+        validateInternalStates();
+
+        return mIDescrambler.asBinder();
+    }
+
+    /*
+     * TODO: handle ServiceSpecificException from the mIDescrambler
+     * All Drm-specific failures will be thrown by mIDescrambler as
+     * ServiceSpecificException exception with Drm error code.
+     * These need to be re-thrown as crypto exceptions.
+     */
+
+    /**
+     * Query if the scrambling scheme requires the use of a secure decoder
+     * to decode data of the given mime type.
+     *
+     * @param mime The mime type of the media data
+     *
+     * @throws IllegalStateException if the descrambler instance is not valid.
+     */
+    public final boolean requiresSecureDecoderComponent(@NonNull String mime) {
+        validateInternalStates();
+
+        try {
+            return mIDescrambler.requiresSecureDecoderComponent(mime);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+        return true;
+    }
+
+    /**
+     * Associate a MediaCas session with this MediaDescrambler instance.
+     * The MediaCas session is used to securely load decryption keys for
+     * the descrambler. The crypto keys loaded through the MediaCas session
+     * may be selected for use during the descrambling operation performed
+     * by {@link android.media.MediaExtractor or @link
+     * 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
+     * MediaDescrambler instance.
+     *
+     * @throws IllegalStateException if the descrambler instance is not valid,
+     * or IllegalArgumentException if the sessionId is not valid.
+     */
+    public final void setMediaCasSession(@NonNull byte[] sessionId) {
+        validateInternalStates();
+
+        try {
+            mIDescrambler.setMediaCasSession(sessionId);
+        } catch (RemoteException e) {
+            cleanupAndRethrowIllegalState();
+        }
+    }
+
+    /**
+     * 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 cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
+     * describing the subsamples contained in src.
+     *
+     * @return number of bytes that have been successfully descrambled, with negative
+     * values indicating errors.
+     *
+     * @throws IllegalStateException if the descrambler instance is not valid.
+     */
+    /*
+     * TODO: throw DRM-specific exception if decrambling is failing.
+     */
+    public final int descramble(
+            @NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
+            @NonNull MediaCodec.CryptoInfo cryptoInfo) {
+        validateInternalStates();
+
+        if (cryptoInfo.numSubSamples <= 0) {
+            throw new IllegalArgumentException(
+                    "Invalid CryptoInfo: invalid numSubSamples=" + cryptoInfo.numSubSamples);
+        } else if (cryptoInfo.numBytesOfClearData == null
+                && cryptoInfo.numBytesOfEncryptedData == null) {
+            throw new IllegalArgumentException(
+                    "Invalid CryptoInfo: clearData and encryptedData size arrays are both null!");
+        } else if (cryptoInfo.numBytesOfClearData != null
+                && cryptoInfo.numBytesOfClearData.length < cryptoInfo.numSubSamples) {
+            throw new IllegalArgumentException(
+                    "Invalid CryptoInfo: numBytesOfClearData is too small!");
+        } else if (cryptoInfo.numBytesOfEncryptedData != null
+                && cryptoInfo.numBytesOfEncryptedData.length < cryptoInfo.numSubSamples) {
+            throw new IllegalArgumentException(
+                    "Invalid CryptoInfo: numBytesOfEncryptedData is too small!");
+        } else if (cryptoInfo.key == null || cryptoInfo.key.length != 16) {
+            throw new IllegalArgumentException(
+                    "Invalid CryptoInfo: key array is invalid!");
+        }
+
+        return native_descramble(
+                cryptoInfo.key[0],
+                cryptoInfo.numSubSamples,
+                cryptoInfo.numBytesOfClearData,
+                cryptoInfo.numBytesOfEncryptedData,
+                srcBuf, srcPos, dstBuf, dstPos);
+    }
+
+    public final void release() {
+        if (mIDescrambler != null) {
+            try {
+                mIDescrambler.release();
+            } catch (RemoteException e) {
+            } finally {
+                mIDescrambler = null;
+            }
+        }
+        native_release();
+    }
+
+    @Override
+    protected void finalize() {
+        release();
+    }
+
+    private static native final void native_init();
+    private native final void native_setup(@NonNull IBinder decramblerBinder);
+    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);
+
+    static {
+        System.loadLibrary("media_jni");
+        native_init();
+    }
+
+    private long mNativeContext;
+}
\ No newline at end of file
index 2ca36ea..01ae36f 100644 (file)
@@ -247,6 +247,22 @@ final public class MediaExtractor {
     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.
+     */
+    public final void setMediaCas(@NonNull MediaCas mediaCas) {
+        nativeSetMediaCas(mediaCas.getBinder());
+    }
+
+    private native final void nativeSetMediaCas(@NonNull IBinder casBinder);
+
     @Override
     protected void finalize() {
         native_finalize();
@@ -290,6 +306,31 @@ 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) {
@@ -307,8 +348,8 @@ final public class MediaExtractor {
                     }
                 };
             }
+            return null;
         }
-        return null;
     }
 
     /**
@@ -557,12 +598,22 @@ final public class MediaExtractor {
      */
     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 {}
index d74aa81..4791cf0 100644 (file)
@@ -105,6 +105,8 @@ public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
     public static final String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
     public static final String MIMETYPE_VIDEO_RAW = "video/raw";
+    public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
+    public static final String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
 
     public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
     public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
@@ -120,7 +122,7 @@ public final class MediaFormat {
     public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
     public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
     public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
-    public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
+    public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
 
     /**
      * MIME type for WebVTT subtitle data.
diff --git a/media/java/android/media/UnsupportedCasException.java b/media/java/android/media/UnsupportedCasException.java
new file mode 100644 (file)
index 0000000..3167637
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Exception thrown when an attempt is made to construct a MediaCas object
+ * using a CA_system_id that is not supported by the device
+ */
+public final class UnsupportedCasException extends MediaCasException {
+    public UnsupportedCasException(String detailMessage) {
+        super(detailMessage);
+    }
+}
index f69313c..861ed0a 100644 (file)
@@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \
     android_media_MediaCodec.cpp \
     android_media_MediaCodecList.cpp \
     android_media_MediaDataSource.cpp \
+    android_media_MediaDescrambler.cpp \
     android_media_MediaDrm.cpp \
     android_media_MediaExtractor.cpp \
     android_media_MediaHTTPConnection.cpp \
index 6f9883c..293e5dd 100644 (file)
 #include "android_media_Utils.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
+#include "android_util_Binder.h"
 #include "jni.h"
 #include "JNIHelp.h"
 
+#include <android/media/IDescrambler.h>
+
 #include <cutils/compiler.h>
 
 #include <gui/Surface.h>
@@ -269,6 +272,7 @@ status_t JMediaCodec::configure(
         const sp<AMessage> &format,
         const sp<IGraphicBufferProducer> &bufferProducer,
         const sp<ICrypto> &crypto,
+        const sp<IDescrambler> &descrambler,
         int flags) {
     sp<Surface> client;
     if (bufferProducer != NULL) {
@@ -278,7 +282,8 @@ status_t JMediaCodec::configure(
         mSurfaceTextureClient.clear();
     }
 
-    return mCodec->configure(format, mSurfaceTextureClient, crypto, flags);
+    return mCodec->configure(
+            format, mSurfaceTextureClient, crypto, descrambler, flags);
 }
 
 status_t JMediaCodec::setSurface(
@@ -967,6 +972,7 @@ static void android_media_MediaCodec_native_configure(
         jobjectArray keys, jobjectArray values,
         jobject jsurface,
         jobject jcrypto,
+        jobject descramblerBinderObj,
         jint flags) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
@@ -1002,7 +1008,13 @@ static void android_media_MediaCodec_native_configure(
         crypto = JCrypto::GetCrypto(env, jcrypto);
     }
 
-    err = codec->configure(format, bufferProducer, crypto, flags);
+    sp<IDescrambler> descrambler;
+    if (descramblerBinderObj != NULL) {
+        sp<IBinder> binder = ibinderForJavaObject(env, descramblerBinderObj);
+        descrambler = interface_cast<IDescrambler>(binder);
+    }
+
+    err = codec->configure(format, bufferProducer, crypto, descrambler, flags);
 
     throwExceptionAsNecessary(env, err);
 }
@@ -1942,7 +1954,7 @@ static const JNINativeMethod gMethods[] = {
 
     { "native_configure",
       "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
-      "Landroid/media/MediaCrypto;I)V",
+      "Landroid/media/MediaCrypto;Landroid/os/IBinder;I)V",
       (void *)android_media_MediaCodec_native_configure },
 
     { "native_setSurface",
index b3b1b3a..a8c76c5 100644 (file)
@@ -35,6 +35,10 @@ class IGraphicBufferProducer;
 struct MediaCodec;
 struct PersistentSurface;
 class Surface;
+namespace media {
+class IDescrambler;
+};
+using namespace media;
 
 struct JMediaCodec : public AHandler {
     JMediaCodec(
@@ -54,6 +58,7 @@ struct JMediaCodec : public AHandler {
             const sp<AMessage> &format,
             const sp<IGraphicBufferProducer> &bufferProducer,
             const sp<ICrypto> &crypto,
+            const sp<IDescrambler> &descrambler,
             int flags);
 
     status_t setSurface(
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
new file mode 100644 (file)
index 0000000..7585664
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaDescrambler-JNI"
+#include <utils/Log.h>
+
+#include "android_media_MediaDescrambler.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_util_Binder.h"
+#include "JNIHelp.h"
+
+#include <android/media/IDescrambler.h>
+#include <binder/MemoryDealer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+namespace android {
+using media::MediaDescrambler::DescrambleInfo;
+
+struct fields_t {
+    jfieldID context;
+};
+
+static fields_t gFields;
+
+static sp<JDescrambler> getDescrambler(JNIEnv *env, jobject thiz) {
+    return (JDescrambler *)env->GetLongField(thiz, gFields.context);
+}
+
+static void setDescrambler(
+        JNIEnv *env, jobject thiz, const sp<JDescrambler> &descrambler) {
+    sp<JDescrambler> old = (JDescrambler *)env->GetLongField(thiz, gFields.context);
+    if (descrambler != NULL) {
+        descrambler->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetLongField(thiz, gFields.context, (jlong)descrambler.get());
+}
+
+static status_t getBufferAndSize(
+        JNIEnv *env, jobject byteBuf, jint offset, 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"));
+    CHECK(byteBufClass.get() != NULL);
+
+    if (ptr == NULL) {
+        jmethodID arrayID =
+            env->GetMethodID(byteBufClass.get(), "array", "()[B");
+        CHECK(arrayID != NULL);
+
+        byteArray =
+            (jbyteArray)env->CallObjectMethod(byteBuf, arrayID);
+
+        if (byteArray == NULL) {
+            return INVALID_OPERATION;
+        }
+
+        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 (byteArray != NULL) {
+            env->ReleaseByteArrayElements(byteArray, (jbyte *)ptr, 0);
+        }
+
+        return -ERANGE;
+    }
+
+    *outPtr = ptr;
+    *outByteArray = byteArray;
+
+    return OK;
+}
+
+JDescrambler::JDescrambler(JNIEnv *env, jobject descramblerBinderObj) {
+    sp<IDescrambler> cas;
+    if (descramblerBinderObj != NULL) {
+        sp<IBinder> binder = ibinderForJavaObject(env, descramblerBinderObj);
+        mDescrambler = interface_cast<IDescrambler>(binder);
+    }
+}
+
+JDescrambler::~JDescrambler() {
+    // Don't call release() here, it's called by Java class
+    mDescrambler.clear();
+    mMem.clear();
+    mDealer.clear();
+}
+
+void JDescrambler::ensureBufferCapacity(size_t neededSize) {
+    if (mMem != NULL && mMem->size() >= neededSize) {
+        return;
+    }
+
+    ALOGV("ensureBufferCapacity: current size %zu, new size %zu",
+            mMem == NULL ? 0 : mMem->size(), neededSize);
+
+    size_t alignment = MemoryDealer::getAllocationAlignment();
+    neededSize = (neededSize + (alignment - 1)) & ~(alignment - 1);
+    // Align to multiples of 64K.
+    neededSize = (neededSize + 65535) & ~65535;
+    mDealer = new MemoryDealer(neededSize, "JDescrambler");
+    mMem = mDealer->allocate(neededSize);
+}
+
+ssize_t JDescrambler::descramble(
+        jbyte key,
+        size_t numSubSamples,
+        ssize_t totalLength,
+        DescramblerPlugin::SubSample *subSamples,
+        const void *srcPtr,
+        jint srcOffset,
+        void *dstPtr,
+        jint dstOffset) {
+    // TODO: IDescrambler::descramble() is re-entrant, however because we
+    // only have 1 shared mem buffer, we can only do 1 descramble at a time.
+    // Concurrency might be improved by allowing on-demand allocation of up
+    // to 2 shared mem buffers.
+    Mutex::Autolock autolock(mSharedMemLock);
+
+    ensureBufferCapacity(totalLength);
+
+    memcpy(mMem->pointer(),
+            (const void*)((const uint8_t*)srcPtr + srcOffset), totalLength);
+
+    DescrambleInfo info;
+    info.dstType = DescrambleInfo::kDestinationTypeVmPointer;
+    info.numSubSamples = numSubSamples;
+    info.scramblingControl = (DescramblerPlugin::ScramblingControl) key;
+    info.subSamples = subSamples;
+    info.srcMem = mMem;
+    info.srcOffset = 0;
+    info.dstPtr = NULL;
+    info.dstOffset = 0;
+
+    int32_t result;
+    binder::Status status = mDescrambler->descramble(info, &result);
+
+    if (!status.isOk() || result > totalLength) {
+        return -1;
+    }
+    if (result > 0) {
+        memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), result);
+    }
+    return result;
+}
+
+}  // namespace android
+
+using namespace android;
+
+static void android_media_MediaDescrambler_native_release(JNIEnv *env, jobject thiz) {
+    setDescrambler(env, thiz, NULL);
+}
+
+static void android_media_MediaDescrambler_native_init(JNIEnv *env) {
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaDescrambler"));
+    CHECK(clazz.get() != NULL);
+
+    gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+    CHECK(gFields.context != NULL);
+}
+
+static void android_media_MediaDescrambler_native_setup(
+        JNIEnv *env, jobject thiz, jobject descramblerBinderObj) {
+    setDescrambler(env, thiz, new JDescrambler(env, descramblerBinderObj));
+}
+
+static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples,
+        jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
+        DescramblerPlugin::SubSample **outSubSamples) {
+
+    if (numSubSamples <= 0 || numSubSamples >=
+            (signed)(INT32_MAX / sizeof(DescramblerPlugin::SubSample)) ) {
+        // subSamples array may silently overflow if number of samples are
+        // too large.  Use INT32_MAX as maximum allocation size may be less
+        // than SIZE_MAX on some platforms.
+        ALOGE("numSubSamples is invalid!");
+        return -1;
+    }
+
+    jboolean isCopy;
+    ssize_t totalSize = 0;
+
+    jint *numBytesOfClearData =
+        (numBytesOfClearDataObj == NULL)
+            ? NULL
+            : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy);
+
+    jint *numBytesOfEncryptedData =
+        (numBytesOfEncryptedDataObj == NULL)
+            ? NULL
+            : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy);
+
+    DescramblerPlugin::SubSample *subSamples =
+            new(std::nothrow) DescramblerPlugin::SubSample[numSubSamples];
+
+    if (subSamples == NULL) {
+        ALOGE("Failed to allocate SubSample array!");
+        return -1;
+    }
+
+    for (jint i = 0; i < numSubSamples; ++i) {
+        subSamples[i].mNumBytesOfClearData =
+            (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i];
+
+        subSamples[i].mNumBytesOfEncryptedData =
+            (numBytesOfEncryptedData == NULL)
+                ? 0 : numBytesOfEncryptedData[i];
+
+        totalSize += subSamples[i].mNumBytesOfClearData +
+                subSamples[i].mNumBytesOfEncryptedData;
+    }
+
+    if (numBytesOfEncryptedData != NULL) {
+        env->ReleaseIntArrayElements(
+                numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0);
+        numBytesOfEncryptedData = NULL;
+    }
+
+    if (numBytesOfClearData != NULL) {
+        env->ReleaseIntArrayElements(
+                numBytesOfClearDataObj, numBytesOfClearData, 0);
+        numBytesOfClearData = NULL;
+    }
+
+    *outSubSamples = subSamples;
+
+    return totalSize;
+}
+
+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) {
+    sp<JDescrambler> descrambler = getDescrambler(env, thiz);
+    if (descrambler == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1;
+    }
+
+    DescramblerPlugin::SubSample *subSamples = NULL;
+    ssize_t totalLength = getSubSampleInfo(
+            env, numSubSamples, numBytesOfClearDataObj,
+            numBytesOfEncryptedDataObj, &subSamples);
+    if (totalLength < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "Invalid sub sample info!");
+        return -1;
+    }
+
+    ssize_t result = -1;
+    void *srcPtr = NULL, *dstPtr = NULL;
+    jbyteArray srcArray = NULL, dstArray = NULL;
+    status_t err = getBufferAndSize(
+            env, srcBuf, srcOffset, totalLength, &srcPtr, &srcArray);
+
+    if (err == OK) {
+        if (dstBuf == NULL) {
+            dstPtr = srcPtr;
+        } else {
+            err = getBufferAndSize(
+                    env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
+        }
+    }
+
+    if (err == OK) {
+        result = descrambler->descramble(
+                key, numSubSamples, totalLength, subSamples,
+                srcPtr, srcOffset, dstPtr, dstOffset);
+    }
+
+    delete[] subSamples;
+    if (srcArray != NULL) {
+        env->ReleaseByteArrayElements(srcArray, (jbyte *)srcPtr, 0);
+    }
+    if (dstArray != NULL) {
+        env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
+    }
+    return result;
+}
+
+static const JNINativeMethod gMethods[] = {
+    { "native_release", "()V",
+            (void *)android_media_MediaDescrambler_native_release },
+    { "native_init", "()V",
+            (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",
+            (void *)android_media_MediaDescrambler_native_descramble },
+};
+
+int register_android_media_Descrambler(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                "android/media/MediaDescrambler", gMethods, NELEM(gMethods));
+}
+
diff --git a/media/jni/android_media_MediaDescrambler.h b/media/jni/android_media_MediaDescrambler.h
new file mode 100644 (file)
index 0000000..e944a90
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+#ifndef _ANDROID_MEDIA_DESCRAMBLER_H_
+#define _ANDROID_MEDIA_DESCRAMBLER_H_
+
+#include "jni.h"
+
+#include <media/cas/DescramblerAPI.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+
+namespace android {
+class IMemory;
+class MemoryDealer;
+namespace media {
+class IDescrambler;
+};
+using namespace media;
+
+struct JDescrambler : public RefBase {
+    JDescrambler(JNIEnv *env, jobject descramberBinderObj);
+
+    ssize_t descramble(
+            jbyte key,
+            size_t numSubSamples,
+            ssize_t totalLength,
+            DescramblerPlugin::SubSample *subSamples,
+            const void *srcPtr,
+            jint srcOffset,
+            void *dstPtr,
+            jint dstOffset);
+
+protected:
+    virtual ~JDescrambler();
+
+private:
+    sp<IDescrambler> mDescrambler;
+    sp<IMemory> mMem;
+    sp<MemoryDealer> mDealer;
+    Mutex mSharedMemLock;
+
+    void ensureBufferCapacity(size_t neededSize);
+
+    DISALLOW_EVIL_CONSTRUCTORS(JDescrambler);
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_DESCRAMBLER_H_
index 2008f8d..3c33493 100644 (file)
@@ -37,6 +37,7 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/NuMediaExtractor.h>
+#include <android/media/ICas.h>
 
 #include <nativehelper/ScopedLocalRef.h>
 
@@ -88,6 +89,10 @@ status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) {
     return mImpl->setDataSource(datasource);
 }
 
+status_t JMediaExtractor::setMediaCas(const sp<ICas> &cas) {
+    return mImpl->setMediaCas(cas);
+}
+
 size_t JMediaExtractor::countTracks() const {
     return mImpl->countTracks();
 }
@@ -734,6 +739,36 @@ static void android_media_MediaExtractor_setDataSourceCallback(
     }
 }
 
+static void android_media_MediaExtractor_setMediaCas(
+        JNIEnv *env, jobject thiz, jobject casBinderObj) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    if (casBinderObj == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    sp<ICas> cas;
+    if (casBinderObj != NULL) {
+        sp<IBinder> binder = ibinderForJavaObject(env, casBinderObj);
+        cas = interface_cast<ICas>(binder);
+    }
+    status_t err = extractor->setMediaCas(cas);
+
+    if (err != OK) {
+        cas.clear();
+        jniThrowException(
+                env,
+                "java/io/IllegalArgumentException",
+                "Failed to set MediaCas on extractor.");
+    }
+}
+
 static jlong android_media_MediaExtractor_getCachedDurationUs(
         JNIEnv *env, jobject thiz) {
     sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
@@ -861,6 +896,9 @@ static const JNINativeMethod gMethods[] = {
     { "setDataSource", "(Landroid/media/MediaDataSource;)V",
       (void *)android_media_MediaExtractor_setDataSourceCallback },
 
+    { "nativeSetMediaCas", "(Landroid/os/IBinder;)V",
+      (void *)android_media_MediaExtractor_setMediaCas },
+
     { "getCachedDuration", "()J",
       (void *)android_media_MediaExtractor_getCachedDurationUs },
 
index c747ef5..3d8c50b 100644 (file)
 #include "jni.h"
 
 namespace android {
+namespace media {
+class ICas;
+};
+using namespace media;
 
 struct IMediaHTTPService;
 class MetaData;
@@ -44,6 +48,8 @@ struct JMediaExtractor : public RefBase {
     status_t setDataSource(int fd, off64_t offset, off64_t size);
     status_t setDataSource(const sp<DataSource> &source);
 
+    status_t setMediaCas(const sp<ICas> &cas);
+
     size_t countTracks() const;
     status_t getTrackFormat(size_t index, jobject *format) const;
 
index 636727e..27724a1 100644 (file)
@@ -1445,6 +1445,7 @@ extern int register_android_media_ImageReader(JNIEnv *env);
 extern int register_android_media_ImageWriter(JNIEnv *env);
 extern int register_android_media_Crypto(JNIEnv *env);
 extern int register_android_media_Drm(JNIEnv *env);
+extern int register_android_media_Descrambler(JNIEnv *env);
 extern int register_android_media_MediaCodec(JNIEnv *env);
 extern int register_android_media_MediaExtractor(JNIEnv *env);
 extern int register_android_media_MediaCodecList(JNIEnv *env);
@@ -1561,6 +1562,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
         goto bail;
     }
 
+    if (register_android_media_Descrambler(env) < 0) {
+        ALOGE("ERROR: MediaDescrambler native registration failed");
+        goto bail;
+    }
+
     if (register_android_media_MediaHTTPConnection(env) < 0) {
         ALOGE("ERROR: MediaHTTPConnection native registration failed");
         goto bail;