diff --git a/Android.mk b/Android.mk index e372fdaf0aae0..04761c1f55d2e 100644 --- a/Android.mk +++ b/Android.mk @@ -408,6 +408,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 \ diff --git a/api/current.txt b/api/current.txt index 4270b4faba8b5..62365cce20a93 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21471,8 +21471,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; @@ -21512,6 +21546,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 @@ -21668,6 +21703,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; @@ -21892,6 +21928,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(); @@ -22048,8 +22092,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 @@ -22146,6 +22192,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"; @@ -22156,6 +22203,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"; } @@ -23127,6 +23175,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/api/system-current.txt b/api/system-current.txt index 75095b3cf974c..8626f1b33fa4b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -23159,8 +23159,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; @@ -23200,6 +23234,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 @@ -23356,6 +23391,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; @@ -23580,6 +23616,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(); @@ -23736,8 +23780,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 @@ -23834,6 +23880,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"; @@ -23844,6 +23891,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"; } @@ -24826,6 +24874,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/api/test-current.txt b/api/test-current.txt index af8bb7f92bdc9..44386bec120ad 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -21567,8 +21567,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; @@ -21608,6 +21642,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 @@ -21764,6 +21799,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; @@ -21988,6 +22024,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(); @@ -22144,8 +22188,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 @@ -22242,6 +22288,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"; @@ -22252,6 +22299,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"; } @@ -23223,6 +23271,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 index 0000000000000..6b2ce4ab973df --- /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 0000000000000..01a5abc883b21 --- /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 0000000000000..fdf99eba627dd --- /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 0000000000000..44f682518832e --- /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 0000000000000..cb8d0c65b0b9c --- /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 0000000000000..2e22132614d25 --- /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. + *

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

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

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

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

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

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

+ *

Using Android's MediaExtractor

+ *

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

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

+ * TODO: determine exception handling schemes. + *

+ *

Listeners

+ *

The app may register a listener to receive events from the CA system using + * method {@link #setEventListener}. The exact format of the event is scheme-specific + * and is not specified by this API. + */ +public final class MediaCas { + 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 gDefault = + new Singleton() { + @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 CREATOR + = new Parcelable.Creator() { + 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 CREATOR + = new Parcelable.Creator() { + 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 0000000000000..1d5d3cdb2134f --- /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 75ccffee6f097..1ca96583112e7 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -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 diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 8ada295f529cd..3ac4c3471ee34 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -459,6 +459,11 @@ public final class MediaCodecInfo { */ public static final String FEATURE_TunneledPlayback = "tunneled-playback"; + /** + * video decoder only: codec supports queuing partial frames. + */ + public static final String FEATURE_PartialFrame = "partial-frame"; + /** * video encoder only: codec supports 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 index 0000000000000..e7892442ef1a0 --- /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 0000000000000..f5eede859eb1d --- /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 CREATOR + = new Parcelable.Creator() { + 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 2ca36ea68e479..01ae36ffccfe0 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -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 initDataMap = + new HashMap(); + + int numTracks = getTrackCount(); + for (int i = 0; i < numTracks; ++i) { + Map trackFormatMap = getTrackFormatNative(i); + if (!trackFormatMap.containsKey("cas")) { + continue; + } + ByteBuffer buf = (ByteBuffer) trackFormatMap.get("cas"); + buf.rewind(); + final byte[] data = new byte[buf.remaining()]; + buf.get(data); + initDataMap.put(new UUID(0, i), new DrmInitData.SchemeInitData("cas", data)); + } + if (initDataMap.isEmpty()) { + return null; + } + return new DrmInitData() { + public SchemeInitData get(UUID schemeUuid) { + return initDataMap.get(schemeUuid); + } + }; } else { int numTracks = getTrackCount(); for (int i = 0; i < numTracks; ++i) { @@ -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 {} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index d74aa81a6bd2b..4791cf08dee26 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -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 index 0000000000000..31676373783b2 --- /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 f69313c07c56a..861ed0a3b08ac 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -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 \ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 6f9883c08050c..293e5dd746dad 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 + #include #include @@ -269,6 +272,7 @@ status_t JMediaCodec::configure( const sp &format, const sp &bufferProducer, const sp &crypto, + const sp &descrambler, int flags) { sp 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 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 descrambler; + if (descramblerBinderObj != NULL) { + sp binder = ibinderForJavaObject(env, descramblerBinderObj); + descrambler = interface_cast(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", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index b3b1b3a87ba4e..a8c76c5e61f76 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -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 &format, const sp &bufferProducer, const sp &crypto, + const sp &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 0000000000000..75856648f498f --- /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 + +#include "android_media_MediaDescrambler.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_util_Binder.h" +#include "JNIHelp.h" + +#include +#include +#include +#include + +namespace android { +using media::MediaDescrambler::DescrambleInfo; + +struct fields_t { + jfieldID context; +}; + +static fields_t gFields; + +static sp getDescrambler(JNIEnv *env, jobject thiz) { + return (JDescrambler *)env->GetLongField(thiz, gFields.context); +} + +static void setDescrambler( + JNIEnv *env, jobject thiz, const sp &descrambler) { + sp 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 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 cas; + if (descramblerBinderObj != NULL) { + sp binder = ibinderForJavaObject(env, descramblerBinderObj); + mDescrambler = interface_cast(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 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 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 0000000000000..e944a90dba7ad --- /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 +#include +#include +#include + +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 mDescrambler; + sp mMem; + sp 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 2008f8dfdc7e7..3c3349359e828 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include @@ -88,6 +89,10 @@ status_t JMediaExtractor::setDataSource(const sp &datasource) { return mImpl->setDataSource(datasource); } +status_t JMediaExtractor::setMediaCas(const sp &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 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 cas; + if (casBinderObj != NULL) { + sp binder = ibinderForJavaObject(env, casBinderObj); + cas = interface_cast(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 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 }, diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h index c747ef5939b49..3d8c50b809ac9 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 @@ struct JMediaExtractor : public RefBase { status_t setDataSource(int fd, off64_t offset, off64_t size); status_t setDataSource(const sp &source); + status_t setMediaCas(const sp &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 636727eb0ac01..27724a185c36d 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -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;