Merge "MediaCas: add framework APIs"

This commit is contained in:
Chong Zhang
2017-02-23 22:36:18 +00:00
committed by Android (Google) Code Review
26 changed files with 1851 additions and 7 deletions

View File

@@ -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 \

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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();
}

View 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);
}

View 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();
}

View 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);
}

View 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";

View 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();
}
}

View 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);
}
}

View File

@@ -25,6 +25,7 @@ import android.graphics.SurfaceTexture;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.view.Surface;
@@ -1567,6 +1568,13 @@ final public class MediaCodec {
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
/**
* This indicates that the buffer only contains part of a frame,
* and the decoder should batch the data until a buffer without
* this flag appears before decoding the frame.
*/
public static final int BUFFER_FLAG_PARTIAL_FRAME = 8;
/** @hide */
@IntDef(
flag = true,
@@ -1575,6 +1583,7 @@ final public class MediaCodec {
BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_CODEC_CONFIG,
BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_PARTIAL_FRAME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BufferFlag {}
@@ -1851,6 +1860,48 @@ final public class MediaCodec {
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags) {
configure(format, surface, crypto, null, flags);
}
/**
* Configure a component to be used with a descrambler.
* @param format The format of the input data (decoder) or the desired
* format of the output data (encoder). Passing {@code null}
* as {@code format} is equivalent to passing an
* {@link MediaFormat#MediaFormat an empty mediaformat}.
* @param surface Specify a surface on which to render the output of this
* decoder. Pass {@code null} as {@code surface} if the
* codec does not generate raw video output (e.g. not a video
* decoder) and/or if you want to configure the codec for
* {@link ByteBuffer} output.
* @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
* component as an encoder.
* @param descrambler Specify a descrambler object to facilitate secure
* descrambling of the media data. descrambler must not be
* null if this method is used. For non-secure codecs, use
* {@link #configure} and with null crypto parameter.
* @throws IllegalArgumentException if the surface has been released (or is invalid),
* or the format is unacceptable (e.g. missing a mandatory key),
* or the flags are not set properly
* (e.g. missing {@link #CONFIGURE_FLAG_ENCODE} for an encoder).
* @throws IllegalStateException if not in the Uninitialized state.
* @throws CryptoException upon DRM error.
* @throws CodecException upon codec error.
*/
public void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
@ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
configure(format, surface, null, descrambler.getBinder(), flags);
}
private void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
@Nullable MediaCrypto crypto, @Nullable IBinder descramblerBinder,
@ConfigureFlag int flags) {
if (crypto != null && descramblerBinder != null) {
throw new IllegalArgumentException("Can't use crypto and descrambler together!");
}
String[] keys = null;
Object[] values = null;
@@ -1881,7 +1932,7 @@ final public class MediaCodec {
mHasSurface = surface != null;
native_configure(keys, values, surface, crypto, flags);
native_configure(keys, values, surface, crypto, descramblerBinder, flags);
}
/**
@@ -1959,7 +2010,8 @@ final public class MediaCodec {
private native final void native_configure(
@Nullable String[] keys, @Nullable Object[] values,
@Nullable Surface surface, @Nullable MediaCrypto crypto, @ConfigureFlag int flags);
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@Nullable IBinder descramblerBinder, @ConfigureFlag int flags);
/**
* Requests a Surface to use as the input to an encoder, in place of input buffers. This

View File

@@ -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 = {

View 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";

View 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;
}

View File

@@ -247,6 +247,22 @@ final public class MediaExtractor {
public native final void setDataSource(
@NonNull FileDescriptor fd, long offset, long length) throws IOException;
/**
* Sets the MediaCas instance to use. This should be called after a
* successful setDataSource() if at least one track reports mime type
* of {@link android.media.MediaFormat#MIMETYPE_AUDIO_SCRAMBLED}
* or {@link android.media.MediaFormat#MIMETYPE_VIDEO_SCRAMBLED}.
* Stream parsing will not proceed until a valid MediaCas object
* is provided.
*
* @param mediaCas the MediaCas object to use.
*/
public final void setMediaCas(@NonNull MediaCas mediaCas) {
nativeSetMediaCas(mediaCas.getBinder());
}
private native final void nativeSetMediaCas(@NonNull IBinder casBinder);
@Override
protected void finalize() {
native_finalize();
@@ -290,6 +306,31 @@ final public class MediaExtractor {
return initDataMap.get(schemeUuid);
}
};
} else if (formatMap.containsKey("mime")
&& "video/mp2ts".equals(formatMap.get("mime"))) {
final Map<UUID, DrmInitData.SchemeInitData> initDataMap =
new HashMap<UUID, DrmInitData.SchemeInitData>();
int numTracks = getTrackCount();
for (int i = 0; i < numTracks; ++i) {
Map<String, Object> trackFormatMap = getTrackFormatNative(i);
if (!trackFormatMap.containsKey("cas")) {
continue;
}
ByteBuffer buf = (ByteBuffer) trackFormatMap.get("cas");
buf.rewind();
final byte[] data = new byte[buf.remaining()];
buf.get(data);
initDataMap.put(new UUID(0, i), new DrmInitData.SchemeInitData("cas", data));
}
if (initDataMap.isEmpty()) {
return null;
}
return new DrmInitData() {
public SchemeInitData get(UUID schemeUuid) {
return initDataMap.get(schemeUuid);
}
};
} else {
int numTracks = getTrackCount();
for (int i = 0; i < numTracks; ++i) {
@@ -307,8 +348,8 @@ final public class MediaExtractor {
}
};
}
return null;
}
return null;
}
/**
@@ -557,12 +598,22 @@ final public class MediaExtractor {
*/
public static final int SAMPLE_FLAG_ENCRYPTED = 2;
/**
* This indicates that the buffer only contains part of a frame,
* and the decoder should batch the data until a buffer without
* this flag appears before decoding the frame.
*
* @see MediaCodec#BUFFER_FLAG_PARTIAL_FRAME
*/
public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4;
/** @hide */
@IntDef(
flag = true,
value = {
SAMPLE_FLAG_SYNC,
SAMPLE_FLAG_ENCRYPTED,
SAMPLE_FLAG_PARTIAL_FRAME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SampleFlag {}

View File

@@ -105,6 +105,8 @@ public final class MediaFormat {
public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
public static final String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
public static final String MIMETYPE_VIDEO_RAW = "video/raw";
public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
public static final String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
@@ -120,7 +122,7 @@ public final class MediaFormat {
public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
/**
* MIME type for WebVTT subtitle data.

View 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);
}
}

View File

@@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \
android_media_MediaCodec.cpp \
android_media_MediaCodecList.cpp \
android_media_MediaDataSource.cpp \
android_media_MediaDescrambler.cpp \
android_media_MediaDrm.cpp \
android_media_MediaExtractor.cpp \
android_media_MediaHTTPConnection.cpp \

View File

@@ -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",

View File

@@ -35,6 +35,10 @@ class IGraphicBufferProducer;
struct MediaCodec;
struct PersistentSurface;
class Surface;
namespace media {
class IDescrambler;
};
using namespace media;
struct JMediaCodec : public AHandler {
JMediaCodec(
@@ -54,6 +58,7 @@ struct JMediaCodec : public AHandler {
const sp<AMessage> &format,
const sp<IGraphicBufferProducer> &bufferProducer,
const sp<ICrypto> &crypto,
const sp<IDescrambler> &descrambler,
int flags);
status_t setSurface(

View 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));
}

View 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_

View File

@@ -37,6 +37,7 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/NuMediaExtractor.h>
#include <android/media/ICas.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -88,6 +89,10 @@ status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) {
return mImpl->setDataSource(datasource);
}
status_t JMediaExtractor::setMediaCas(const sp<ICas> &cas) {
return mImpl->setMediaCas(cas);
}
size_t JMediaExtractor::countTracks() const {
return mImpl->countTracks();
}
@@ -734,6 +739,36 @@ static void android_media_MediaExtractor_setDataSourceCallback(
}
}
static void android_media_MediaExtractor_setMediaCas(
JNIEnv *env, jobject thiz, jobject casBinderObj) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
if (extractor == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
}
if (casBinderObj == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
sp<ICas> cas;
if (casBinderObj != NULL) {
sp<IBinder> binder = ibinderForJavaObject(env, casBinderObj);
cas = interface_cast<ICas>(binder);
}
status_t err = extractor->setMediaCas(cas);
if (err != OK) {
cas.clear();
jniThrowException(
env,
"java/io/IllegalArgumentException",
"Failed to set MediaCas on extractor.");
}
}
static jlong android_media_MediaExtractor_getCachedDurationUs(
JNIEnv *env, jobject thiz) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
@@ -861,6 +896,9 @@ static const JNINativeMethod gMethods[] = {
{ "setDataSource", "(Landroid/media/MediaDataSource;)V",
(void *)android_media_MediaExtractor_setDataSourceCallback },
{ "nativeSetMediaCas", "(Landroid/os/IBinder;)V",
(void *)android_media_MediaExtractor_setMediaCas },
{ "getCachedDuration", "()J",
(void *)android_media_MediaExtractor_getCachedDurationUs },

View File

@@ -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;

View File

@@ -1445,6 +1445,7 @@ extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_Descrambler(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
@@ -1561,6 +1562,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
goto bail;
}
if (register_android_media_Descrambler(env) < 0) {
ALOGE("ERROR: MediaDescrambler native registration failed");
goto bail;
}
if (register_android_media_MediaHTTPConnection(env) < 0) {
ALOGE("ERROR: MediaHTTPConnection native registration failed");
goto bail;