MediaCas: add framework APIs
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
diff --git a/Android.mk b/Android.mk
index b98d3bc..0cf74b8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -409,6 +409,10 @@
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 \
diff --git a/api/current.txt b/api/current.txt
index 202ea21..c41dff7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21467,8 +21467,42 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/api/system-current.txt b/api/system-current.txt
index 2f67204..1492261 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -23155,8 +23155,42 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/api/test-current.txt b/api/test-current.txt
index 41f279b..d52b780 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21563,8 +21563,42 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
index 0000000..6b2ce4a
--- /dev/null
+++ b/media/java/android/media/ICas.aidl
@@ -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
index 0000000..01a5abc
--- /dev/null
+++ b/media/java/android/media/ICasListener.aidl
@@ -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
index 0000000..fdf99eb
--- /dev/null
+++ b/media/java/android/media/IDescrambler.aidl
@@ -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
index 0000000..44f6825
--- /dev/null
+++ b/media/java/android/media/IMediaCasService.aidl
@@ -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
index 0000000..cb8d0c6
--- /dev/null
+++ b/media/java/android/media/MediaCas.aidl
@@ -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
index 0000000..2e22132
--- /dev/null
+++ b/media/java/android/media/MediaCas.java
@@ -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
index 0000000..1d5d3cd
--- /dev/null
+++ b/media/java/android/media/MediaCasException.java
@@ -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);
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 75ccffee..1ca9658 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,6 +25,7 @@
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 @@
*/
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 @@
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 @@
@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 @@
mHasSurface = surface != null;
- native_configure(keys, values, surface, crypto, flags);
+ native_configure(keys, values, surface, crypto, descramblerBinder, flags);
}
/**
@@ -1959,7 +2010,8 @@
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
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 8ada295f..3ac4c34 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -460,6 +460,11 @@
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 @@
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
index 0000000..e789244
--- /dev/null
+++ b/media/java/android/media/MediaDescrambler.aidl
@@ -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
index 0000000..f5eede8
--- /dev/null
+++ b/media/java/android/media/MediaDescrambler.java
@@ -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
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 2ca36ea..01ae36f 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -247,6 +247,22 @@
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 @@
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 @@
}
};
}
+ return null;
}
- return null;
}
/**
@@ -557,12 +598,22 @@
*/
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 {}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index d74aa81..4791cf0 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -105,6 +105,8 @@
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 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
index 0000000..3167637
--- /dev/null
+++ b/media/java/android/media/UnsupportedCasException.java
@@ -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);
+ }
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index f69313c..861ed0a 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -8,6 +8,7 @@
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 \
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 6f9883c..293e5dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -25,9 +25,12 @@
#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 @@
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 @@
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 @@
jobjectArray keys, jobjectArray values,
jobject jsurface,
jobject jcrypto,
+ jobject descramblerBinderObj,
jint flags) {
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -1002,7 +1008,13 @@
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 @@
{ "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",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index b3b1b3a..a8c76c5 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -35,6 +35,10 @@
struct MediaCodec;
struct PersistentSurface;
class Surface;
+namespace media {
+class IDescrambler;
+};
+using namespace media;
struct JMediaCodec : public AHandler {
JMediaCodec(
@@ -54,6 +58,7 @@
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
index 0000000..7585664
--- /dev/null
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -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
index 0000000..e944a90
--- /dev/null
+++ b/media/jni/android_media_MediaDescrambler.h
@@ -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_
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 2008f8d..3c33493 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -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 @@
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_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 @@
{ "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 },
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index c747ef5..3d8c50b 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -28,6 +28,10 @@
#include "jni.h"
namespace android {
+namespace media {
+class ICas;
+};
+using namespace media;
struct IMediaHTTPService;
class MetaData;
@@ -44,6 +48,8 @@
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;
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 636727e..27724a1 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1445,6 +1445,7 @@
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 @@
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;