Merge "MediaCas: add framework APIs"
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
34
media/java/android/media/ICas.aidl
Normal file
34
media/java/android/media/ICas.aidl
Normal file
@@ -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();
|
||||
}
|
||||
22
media/java/android/media/ICasListener.aidl
Normal file
22
media/java/android/media/ICasListener.aidl
Normal file
@@ -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);
|
||||
}
|
||||
27
media/java/android/media/IDescrambler.aidl
Normal file
27
media/java/android/media/IDescrambler.aidl
Normal file
@@ -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();
|
||||
}
|
||||
32
media/java/android/media/IMediaCasService.aidl
Normal file
32
media/java/android/media/IMediaCasService.aidl
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
23
media/java/android/media/MediaCas.aidl
Normal file
23
media/java/android/media/MediaCas.aidl
Normal file
@@ -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";
|
||||
656
media/java/android/media/MediaCas.java
Normal file
656
media/java/android/media/MediaCas.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
26
media/java/android/media/MediaCasException.java
Normal file
26
media/java/android/media/MediaCasException.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -459,6 +459,11 @@ public final class MediaCodecInfo {
|
||||
*/
|
||||
public static final String FEATURE_TunneledPlayback = "tunneled-playback";
|
||||
|
||||
/**
|
||||
* <b>video decoder only</b>: codec supports queuing partial frames.
|
||||
*/
|
||||
public static final String FEATURE_PartialFrame = "partial-frame";
|
||||
|
||||
/**
|
||||
* <b>video encoder only</b>: codec supports intra refresh.
|
||||
*/
|
||||
@@ -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 = {
|
||||
|
||||
20
media/java/android/media/MediaDescrambler.aidl
Normal file
20
media/java/android/media/MediaDescrambler.aidl
Normal file
@@ -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";
|
||||
249
media/java/android/media/MediaDescrambler.java
Normal file
249
media/java/android/media/MediaDescrambler.java
Normal file
@@ -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;
|
||||
}
|
||||
@@ -247,6 +247,22 @@ final public class MediaExtractor {
|
||||
public native final void setDataSource(
|
||||
@NonNull FileDescriptor fd, long offset, long length) throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the MediaCas instance to use. This should be called after a
|
||||
* successful setDataSource() if at least one track reports mime type
|
||||
* of {@link android.media.MediaFormat#MIMETYPE_AUDIO_SCRAMBLED}
|
||||
* or {@link android.media.MediaFormat#MIMETYPE_VIDEO_SCRAMBLED}.
|
||||
* Stream parsing will not proceed until a valid MediaCas object
|
||||
* is provided.
|
||||
*
|
||||
* @param mediaCas the MediaCas object to use.
|
||||
*/
|
||||
public final void setMediaCas(@NonNull MediaCas mediaCas) {
|
||||
nativeSetMediaCas(mediaCas.getBinder());
|
||||
}
|
||||
|
||||
private native final void nativeSetMediaCas(@NonNull IBinder casBinder);
|
||||
|
||||
@Override
|
||||
protected void finalize() {
|
||||
native_finalize();
|
||||
@@ -290,6 +306,31 @@ final public class MediaExtractor {
|
||||
return initDataMap.get(schemeUuid);
|
||||
}
|
||||
};
|
||||
} else if (formatMap.containsKey("mime")
|
||||
&& "video/mp2ts".equals(formatMap.get("mime"))) {
|
||||
final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
|
||||
new HashMap<UUID, DrmInitData.SchemeInitData>();
|
||||
|
||||
int numTracks = getTrackCount();
|
||||
for (int i = 0; i < numTracks; ++i) {
|
||||
Map<String, Object> trackFormatMap = getTrackFormatNative(i);
|
||||
if (!trackFormatMap.containsKey("cas")) {
|
||||
continue;
|
||||
}
|
||||
ByteBuffer buf = (ByteBuffer) trackFormatMap.get("cas");
|
||||
buf.rewind();
|
||||
final byte[] data = new byte[buf.remaining()];
|
||||
buf.get(data);
|
||||
initDataMap.put(new UUID(0, i), new DrmInitData.SchemeInitData("cas", data));
|
||||
}
|
||||
if (initDataMap.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new DrmInitData() {
|
||||
public SchemeInitData get(UUID schemeUuid) {
|
||||
return initDataMap.get(schemeUuid);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
int numTracks = getTrackCount();
|
||||
for (int i = 0; i < numTracks; ++i) {
|
||||
@@ -307,8 +348,8 @@ final public class MediaExtractor {
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,12 +598,22 @@ final public class MediaExtractor {
|
||||
*/
|
||||
public static final int SAMPLE_FLAG_ENCRYPTED = 2;
|
||||
|
||||
/**
|
||||
* This indicates that the buffer only contains part of a frame,
|
||||
* and the decoder should batch the data until a buffer without
|
||||
* this flag appears before decoding the frame.
|
||||
*
|
||||
* @see MediaCodec#BUFFER_FLAG_PARTIAL_FRAME
|
||||
*/
|
||||
public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {
|
||||
SAMPLE_FLAG_SYNC,
|
||||
SAMPLE_FLAG_ENCRYPTED,
|
||||
SAMPLE_FLAG_PARTIAL_FRAME,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SampleFlag {}
|
||||
|
||||
@@ -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.
|
||||
|
||||
27
media/java/android/media/UnsupportedCasException.java
Normal file
27
media/java/android/media/UnsupportedCasException.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 @@ status_t JMediaCodec::configure(
|
||||
const sp<AMessage> &format,
|
||||
const sp<IGraphicBufferProducer> &bufferProducer,
|
||||
const sp<ICrypto> &crypto,
|
||||
const sp<IDescrambler> &descrambler,
|
||||
int flags) {
|
||||
sp<Surface> client;
|
||||
if (bufferProducer != NULL) {
|
||||
@@ -278,7 +282,8 @@ status_t JMediaCodec::configure(
|
||||
mSurfaceTextureClient.clear();
|
||||
}
|
||||
|
||||
return mCodec->configure(format, mSurfaceTextureClient, crypto, flags);
|
||||
return mCodec->configure(
|
||||
format, mSurfaceTextureClient, crypto, descrambler, flags);
|
||||
}
|
||||
|
||||
status_t JMediaCodec::setSurface(
|
||||
@@ -967,6 +972,7 @@ static void android_media_MediaCodec_native_configure(
|
||||
jobjectArray keys, jobjectArray values,
|
||||
jobject jsurface,
|
||||
jobject jcrypto,
|
||||
jobject descramblerBinderObj,
|
||||
jint flags) {
|
||||
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
|
||||
|
||||
@@ -1002,7 +1008,13 @@ static void android_media_MediaCodec_native_configure(
|
||||
crypto = JCrypto::GetCrypto(env, jcrypto);
|
||||
}
|
||||
|
||||
err = codec->configure(format, bufferProducer, crypto, flags);
|
||||
sp<IDescrambler> descrambler;
|
||||
if (descramblerBinderObj != NULL) {
|
||||
sp<IBinder> binder = ibinderForJavaObject(env, descramblerBinderObj);
|
||||
descrambler = interface_cast<IDescrambler>(binder);
|
||||
}
|
||||
|
||||
err = codec->configure(format, bufferProducer, crypto, descrambler, flags);
|
||||
|
||||
throwExceptionAsNecessary(env, err);
|
||||
}
|
||||
@@ -1942,7 +1954,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
|
||||
{ "native_configure",
|
||||
"([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
|
||||
"Landroid/media/MediaCrypto;I)V",
|
||||
"Landroid/media/MediaCrypto;Landroid/os/IBinder;I)V",
|
||||
(void *)android_media_MediaCodec_native_configure },
|
||||
|
||||
{ "native_setSurface",
|
||||
|
||||
@@ -35,6 +35,10 @@ class IGraphicBufferProducer;
|
||||
struct MediaCodec;
|
||||
struct PersistentSurface;
|
||||
class Surface;
|
||||
namespace media {
|
||||
class IDescrambler;
|
||||
};
|
||||
using namespace media;
|
||||
|
||||
struct JMediaCodec : public AHandler {
|
||||
JMediaCodec(
|
||||
@@ -54,6 +58,7 @@ struct JMediaCodec : public AHandler {
|
||||
const sp<AMessage> &format,
|
||||
const sp<IGraphicBufferProducer> &bufferProducer,
|
||||
const sp<ICrypto> &crypto,
|
||||
const sp<IDescrambler> &descrambler,
|
||||
int flags);
|
||||
|
||||
status_t setSurface(
|
||||
|
||||
325
media/jni/android_media_MediaDescrambler.cpp
Normal file
325
media/jni/android_media_MediaDescrambler.cpp
Normal file
@@ -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));
|
||||
}
|
||||
|
||||
64
media/jni/android_media_MediaDescrambler.h
Normal file
64
media/jni/android_media_MediaDescrambler.h
Normal file
@@ -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_
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <media/stagefright/MediaErrors.h>
|
||||
#include <media/stagefright/MetaData.h>
|
||||
#include <media/stagefright/NuMediaExtractor.h>
|
||||
#include <android/media/ICas.h>
|
||||
|
||||
#include <nativehelper/ScopedLocalRef.h>
|
||||
|
||||
@@ -88,6 +89,10 @@ status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) {
|
||||
return mImpl->setDataSource(datasource);
|
||||
}
|
||||
|
||||
status_t JMediaExtractor::setMediaCas(const sp<ICas> &cas) {
|
||||
return mImpl->setMediaCas(cas);
|
||||
}
|
||||
|
||||
size_t JMediaExtractor::countTracks() const {
|
||||
return mImpl->countTracks();
|
||||
}
|
||||
@@ -734,6 +739,36 @@ static void android_media_MediaExtractor_setDataSourceCallback(
|
||||
}
|
||||
}
|
||||
|
||||
static void android_media_MediaExtractor_setMediaCas(
|
||||
JNIEnv *env, jobject thiz, jobject casBinderObj) {
|
||||
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
|
||||
|
||||
if (extractor == NULL) {
|
||||
jniThrowException(env, "java/lang/IllegalStateException", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (casBinderObj == NULL) {
|
||||
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
sp<ICas> cas;
|
||||
if (casBinderObj != NULL) {
|
||||
sp<IBinder> binder = ibinderForJavaObject(env, casBinderObj);
|
||||
cas = interface_cast<ICas>(binder);
|
||||
}
|
||||
status_t err = extractor->setMediaCas(cas);
|
||||
|
||||
if (err != OK) {
|
||||
cas.clear();
|
||||
jniThrowException(
|
||||
env,
|
||||
"java/io/IllegalArgumentException",
|
||||
"Failed to set MediaCas on extractor.");
|
||||
}
|
||||
}
|
||||
|
||||
static jlong android_media_MediaExtractor_getCachedDurationUs(
|
||||
JNIEnv *env, jobject thiz) {
|
||||
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
|
||||
@@ -861,6 +896,9 @@ static const JNINativeMethod gMethods[] = {
|
||||
{ "setDataSource", "(Landroid/media/MediaDataSource;)V",
|
||||
(void *)android_media_MediaExtractor_setDataSourceCallback },
|
||||
|
||||
{ "nativeSetMediaCas", "(Landroid/os/IBinder;)V",
|
||||
(void *)android_media_MediaExtractor_setMediaCas },
|
||||
|
||||
{ "getCachedDuration", "()J",
|
||||
(void *)android_media_MediaExtractor_getCachedDurationUs },
|
||||
|
||||
|
||||
@@ -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<DataSource> &source);
|
||||
|
||||
status_t setMediaCas(const sp<ICas> &cas);
|
||||
|
||||
size_t countTracks() const;
|
||||
status_t getTrackFormat(size_t index, jobject *format) const;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user