1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.NonNull;
20 import android.hardware.cas.V1_0.*;
21 import android.media.MediaCasException.UnsupportedCasException;
22 import android.os.IHwBinder;
23 import android.os.RemoteException;
24 import android.os.ServiceSpecificException;
25 import android.util.Log;
26 
27 import java.nio.ByteBuffer;
28 
29 /**
30  * MediaDescrambler class can be used in conjunction with {@link android.media.MediaCodec}
31  * and {@link android.media.MediaExtractor} to decode media data scrambled by conditional
32  * access (CA) systems such as those in the ISO/IEC13818-1.
33  *
34  * A MediaDescrambler object is initialized from a session opened by a MediaCas object,
35  * and can be used to descramble media streams scrambled with that session's keys.
36  *
37  * Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
38  *
39  */
40 public final class MediaDescrambler implements AutoCloseable {
41     private static final String TAG = "MediaDescrambler";
42     private IDescramblerBase mIDescrambler;
43 
validateInternalStates()44     private final void validateInternalStates() {
45         if (mIDescrambler == null) {
46             throw new IllegalStateException();
47         }
48     }
49 
cleanupAndRethrowIllegalState()50     private final void cleanupAndRethrowIllegalState() {
51         mIDescrambler = null;
52         throw new IllegalStateException();
53     }
54 
55     /**
56      * Instantiate a MediaDescrambler.
57      *
58      * @param CA_system_id The system id of the scrambling scheme.
59      *
60      * @throws UnsupportedCasException if the scrambling scheme is not supported.
61      */
MediaDescrambler(int CA_system_id)62     public MediaDescrambler(int CA_system_id) throws UnsupportedCasException {
63         try {
64             mIDescrambler = MediaCas.getService().createDescrambler(CA_system_id);
65         } catch(Exception e) {
66             Log.e(TAG, "Failed to create descrambler: " + e);
67             mIDescrambler = null;
68         } finally {
69             if (mIDescrambler == null) {
70                 throw new UnsupportedCasException("Unsupported CA_system_id " + CA_system_id);
71             }
72         }
73         native_setup(mIDescrambler.asBinder());
74     }
75 
getBinder()76     IHwBinder getBinder() {
77         validateInternalStates();
78 
79         return mIDescrambler.asBinder();
80     }
81 
82     /**
83      * Query if the scrambling scheme requires the use of a secure decoder
84      * to decode data of the given mime type.
85      *
86      * @param mime The mime type of the media data
87      *
88      * @throws IllegalStateException if the descrambler instance is not valid.
89      */
requiresSecureDecoderComponent(@onNull String mime)90     public final boolean requiresSecureDecoderComponent(@NonNull String mime) {
91         validateInternalStates();
92 
93         try {
94             return mIDescrambler.requiresSecureDecoderComponent(mime);
95         } catch (RemoteException e) {
96             cleanupAndRethrowIllegalState();
97         }
98         return true;
99     }
100 
101     /**
102      * Associate a MediaCas session with this MediaDescrambler instance.
103      * The MediaCas session is used to securely load decryption keys for
104      * the descrambler. The crypto keys loaded through the MediaCas session
105      * may be selected for use during the descrambling operation performed
106      * by {@link android.media.MediaExtractor or @link
107      * android.media.MediaCodec#queueSecureInputBuffer} by specifying even
108      * or odd key in the {@link android.media.MediaCodec.CryptoInfo#key} field.
109      *
110      * @param session the MediaCas session to associate with this
111      * MediaDescrambler instance.
112      *
113      * @throws IllegalStateException if the descrambler instance is not valid.
114      * @throws MediaCasStateException for CAS-specific state exceptions.
115      */
setMediaCasSession(@onNull MediaCas.Session session)116     public final void setMediaCasSession(@NonNull MediaCas.Session session) {
117         validateInternalStates();
118 
119         try {
120             MediaCasStateException.throwExceptionIfNeeded(
121                     mIDescrambler.setMediaCasSession(session.mSessionId));
122         } catch (RemoteException e) {
123             cleanupAndRethrowIllegalState();
124         }
125     }
126 
127     /**
128      * Scramble control value indicating that the samples are not scrambled.
129      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
130      */
131     public static final byte SCRAMBLE_CONTROL_UNSCRAMBLED = 0;
132 
133     /**
134      * Scramble control value reserved and shouldn't be used currently.
135      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
136      */
137     public static final byte SCRAMBLE_CONTROL_RESERVED    = 1;
138 
139     /**
140      * Scramble control value indicating that the even key is used.
141      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
142      */
143     public static final byte SCRAMBLE_CONTROL_EVEN_KEY     = 2;
144 
145     /**
146      * Scramble control value indicating that the odd key is used.
147      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
148      */
149     public static final byte SCRAMBLE_CONTROL_ODD_KEY      = 3;
150 
151     /**
152      * Scramble flag for a hint indicating that the descrambling request is for
153      * retrieving the PES header info only.
154      *
155      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
156      */
157     public static final byte SCRAMBLE_FLAG_PES_HEADER = (1 << 0);
158 
159     /**
160      * Descramble a ByteBuffer of data described by a
161      * {@link android.media.MediaCodec.CryptoInfo} structure.
162      *
163      * @param srcBuf ByteBuffer containing the scrambled data, which starts at
164      * srcBuf.position().
165      * @param dstBuf ByteBuffer to hold the descrambled data, which starts at
166      * dstBuf.position().
167      * @param cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
168      * describing the subsamples contained in srcBuf. The iv and mode fields in
169      * CryptoInfo are not used. key[0] contains the MPEG2TS scrambling control bits
170      * (as defined in ETSI TS 100 289 (2011): "Digital Video Broadcasting (DVB);
171      * Support for use of the DVB Scrambling Algorithm version 3 within digital
172      * broadcasting systems"), and the value must be one of {@link #SCRAMBLE_CONTROL_UNSCRAMBLED},
173      * {@link #SCRAMBLE_CONTROL_RESERVED}, {@link #SCRAMBLE_CONTROL_EVEN_KEY} or
174      * {@link #SCRAMBLE_CONTROL_ODD_KEY}. key[1] is a set of bit flags, with the
175      * only possible bit being {@link #SCRAMBLE_FLAG_PES_HEADER} currently.
176      * key[2~15] are not used.
177      *
178      * @return number of bytes that have been successfully descrambled, with negative
179      * values indicating errors.
180      *
181      * @throws IllegalStateException if the descrambler instance is not valid.
182      * @throws MediaCasStateException for CAS-specific state exceptions.
183      */
descramble( @onNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf, @NonNull MediaCodec.CryptoInfo cryptoInfo)184     public final int descramble(
185             @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
186             @NonNull MediaCodec.CryptoInfo cryptoInfo) {
187         validateInternalStates();
188 
189         if (cryptoInfo.numSubSamples <= 0) {
190             throw new IllegalArgumentException(
191                     "Invalid CryptoInfo: invalid numSubSamples=" + cryptoInfo.numSubSamples);
192         } else if (cryptoInfo.numBytesOfClearData == null
193                 && cryptoInfo.numBytesOfEncryptedData == null) {
194             throw new IllegalArgumentException(
195                     "Invalid CryptoInfo: clearData and encryptedData size arrays are both null!");
196         } else if (cryptoInfo.numBytesOfClearData != null
197                 && cryptoInfo.numBytesOfClearData.length < cryptoInfo.numSubSamples) {
198             throw new IllegalArgumentException(
199                     "Invalid CryptoInfo: numBytesOfClearData is too small!");
200         } else if (cryptoInfo.numBytesOfEncryptedData != null
201                 && cryptoInfo.numBytesOfEncryptedData.length < cryptoInfo.numSubSamples) {
202             throw new IllegalArgumentException(
203                     "Invalid CryptoInfo: numBytesOfEncryptedData is too small!");
204         } else if (cryptoInfo.key == null || cryptoInfo.key.length != 16) {
205             throw new IllegalArgumentException(
206                     "Invalid CryptoInfo: key array is invalid!");
207         }
208 
209         try {
210             return native_descramble(
211                     cryptoInfo.key[0],
212                     cryptoInfo.key[1],
213                     cryptoInfo.numSubSamples,
214                     cryptoInfo.numBytesOfClearData,
215                     cryptoInfo.numBytesOfEncryptedData,
216                     srcBuf, srcBuf.position(), srcBuf.limit(),
217                     dstBuf, dstBuf.position(), dstBuf.limit());
218         } catch (ServiceSpecificException e) {
219             MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
220         } catch (RemoteException e) {
221             cleanupAndRethrowIllegalState();
222         }
223         return -1;
224     }
225 
226     @Override
close()227     public void close() {
228         if (mIDescrambler != null) {
229             try {
230                 mIDescrambler.release();
231             } catch (RemoteException e) {
232             } finally {
233                 mIDescrambler = null;
234             }
235         }
236         native_release();
237     }
238 
239     @Override
finalize()240     protected void finalize() {
241         close();
242     }
243 
native_init()244     private static native final void native_init();
native_setup(@onNull IHwBinder decramblerBinder)245     private native final void native_setup(@NonNull IHwBinder decramblerBinder);
native_release()246     private native final void native_release();
native_descramble( byte key, byte flags, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit, ByteBuffer dstBuf, int dstOffset, int dstLimit)247     private native final int native_descramble(
248             byte key, byte flags, int numSubSamples,
249             int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
250             @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
251             ByteBuffer dstBuf, int dstOffset, int dstLimit) throws RemoteException;
252 
253     static {
254         System.loadLibrary("media_jni");
native_init()255         native_init();
256     }
257 
258     private long mNativeContext;
259 }