1 /*
2 * Copyright (C) 2016 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 #define TAG "NativeMediaDrm"
18
19 #include <utils/Log.h>
20 #include <sys/types.h>
21
22 #include <list>
23 #include <string>
24 #include <vector>
25
26 #include <assert.h>
27 #include <jni.h>
28 #include <nativehelper/JNIHelp.h>
29 #include <sys/stat.h>
30
31 #include <android/native_window_jni.h>
32
33 #include "AMediaObjects.h"
34
35 #include "media/NdkMediaCodec.h"
36 #include "media/NdkMediaCrypto.h"
37 #include "media/NdkMediaDrm.h"
38 #include "media/NdkMediaExtractor.h"
39 #include "media/NdkMediaFormat.h"
40 #include "media/NdkMediaMuxer.h"
41
42 typedef std::vector<uint8_t> Uuid;
43
44 struct fields_t {
45 jfieldID surface;
46 jfieldID mimeType;
47 jfieldID audioUrl;
48 jfieldID videoUrl;
49 };
50
51 struct PlaybackParams {
52 jobject surface;
53 jstring mimeType;
54 jstring audioUrl;
55 jstring videoUrl;
56 };
57
58 static fields_t gFieldIds;
59 static bool gGotVendorDefinedEvent = false;
60 static bool gListenerGotValidExpiryTime = false;
61 static bool gOnKeyChangeListenerOK = false;
62
63 static const char kFileScheme[] = "file://";
64 static constexpr size_t kFileSchemeStrLen = sizeof(kFileScheme) - 1;
65 static constexpr size_t kPlayTimeSeconds = 30;
66 static constexpr size_t kUuidSize = 16;
67
68 static const uint8_t kClearKeyUuid[kUuidSize] = {
69 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
70 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
71 };
72
73 // The test content is not packaged with clearkey UUID,
74 // we have to use a canned clearkey pssh for the test.
75 static const uint8_t kClearkeyPssh[] = {
76 // BMFF box header (4 bytes size + 'pssh')
77 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
78 // full box header (version = 1 flags = 0)
79 0x01, 0x00, 0x00, 0x00,
80 // system id
81 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
82 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
83 // number of key ids
84 0x00, 0x00, 0x00, 0x01,
85 // key id
86 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
87 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
88 // size of data, must be zero
89 0x00, 0x00, 0x00, 0x00
90 };
91
92 static const uint8_t kKeyRequestData[] = {
93 0x7b, 0x22, 0x6b, 0x69, 0x64,
94 0x73, 0x22, 0x3a, 0x5b, 0x22,
95 0x4d, 0x44, 0x41, 0x77, 0x4d,
96 0x44, 0x41, 0x77, 0x4d, 0x44,
97 0x41, 0x77, 0x4d, 0x44, 0x41,
98 0x77, 0x4d, 0x44, 0x41, 0x77,
99 0x4d, 0x41, 0x22, 0x5d, 0x2c,
100 0x22, 0x74, 0x79, 0x70, 0x65,
101 0x22, 0x3a, 0x22, 0x74, 0x65,
102 0x6d, 0x70, 0x6f, 0x72, 0x61,
103 0x72, 0x79, 0x22, 0x7d
104 };
105
106 static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
107
108 // base 64 encoded JSON response string, must not contain padding character '='
109 static const char kResponse[] = "{\"keys\":[{\"kty\":\"oct\"," \
110 "\"kid\":\"MDAwMDAwMDAwMDAwMDAwMA\",\"k\":" \
111 "\"Pwoz80CYueIrwHjgobXoVA\"}]}";
112
isUuidSizeValid(Uuid uuid)113 static bool isUuidSizeValid(Uuid uuid) {
114 return (uuid.size() == kUuidSize);
115 }
116
jbyteArrayToVector(JNIEnv * env,jbyteArray const & byteArray)117 static std::vector<uint8_t> jbyteArrayToVector(
118 JNIEnv* env, jbyteArray const &byteArray) {
119 uint8_t* buffer = reinterpret_cast<uint8_t*>(
120 env->GetByteArrayElements(byteArray, /*is_copy*/NULL));
121 std::vector<uint8_t> vector;
122 for (jsize i = 0; i < env->GetArrayLength(byteArray); ++i) {
123 vector.push_back(buffer[i]);
124 }
125 return vector;
126 }
127
jbyteArrayToUuid(JNIEnv * env,jbyteArray const & uuid)128 static Uuid jbyteArrayToUuid(JNIEnv* env, jbyteArray const &uuid) {
129 Uuid juuid;
130 juuid.resize(0);
131 if (uuid != NULL) {
132 juuid = jbyteArrayToVector(env, uuid);
133 }
134 return juuid;
135 }
136
setDataSourceFdFromUrl(AMediaExtractor * extractor,const char * url)137 static media_status_t setDataSourceFdFromUrl(
138 AMediaExtractor* extractor, const char* url) {
139
140 const char *path = url + kFileSchemeStrLen;
141 FILE* fp = fopen(path, "r");
142 struct stat buf {};
143 media_status_t status = AMEDIA_ERROR_BASE;
144 if (fp && !fstat(fileno(fp), &buf)) {
145 status = AMediaExtractor_setDataSourceFd(
146 extractor,
147 fileno(fp), 0, buf.st_size);
148 } else {
149 status = AMEDIA_ERROR_IO;
150 ALOGE("Failed to convert URL to Fd");
151 }
152 if (fp && fclose(fp) == EOF) {
153 // 0 indicate success, EOF for error
154 ALOGE("Failed to close file pointer");
155 }
156 return status;
157 }
158
setDataSourceFdOrUrl(AMediaExtractor * extractor,const char * url)159 static media_status_t setDataSourceFdOrUrl(
160 AMediaExtractor* extractor, const char* url) {
161
162 media_status_t status = AMEDIA_ERROR_BASE;
163 if (strlen(url) > kFileSchemeStrLen && strncmp(url, kFileScheme, kFileSchemeStrLen) == 0) {
164 status = setDataSourceFdFromUrl(extractor, url);
165 } else {
166 status = AMediaExtractor_setDataSource(extractor, url);
167 }
168 return status;
169 }
170
Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(JNIEnv * env,jclass,jbyteArray uuid)171 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(
172 JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
173
174 if (NULL == uuid) {
175 jniThrowException(env, "java/lang/NullPointerException", "null uuid");
176 return JNI_FALSE;
177 }
178
179 Uuid juuid = jbyteArrayToUuid(env, uuid);
180 if (isUuidSizeValid(juuid)) {
181 return AMediaDrm_isCryptoSchemeSupported(&juuid[0], NULL);
182 } else {
183 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
184 "invalid UUID size, expected %u bytes", kUuidSize);
185 }
186 return JNI_FALSE;
187 }
188
initPlaybackParams(JNIEnv * env,const jobject & playbackParams,PlaybackParams & params)189 void initPlaybackParams(JNIEnv* env, const jobject &playbackParams, PlaybackParams ¶ms) {
190 params.surface = env->GetObjectField(
191 playbackParams, gFieldIds.surface);
192
193 params.mimeType = static_cast<jstring>(env->GetObjectField(
194 playbackParams, gFieldIds.mimeType));
195
196 params.audioUrl = static_cast<jstring>(env->GetObjectField(
197 playbackParams, gFieldIds.audioUrl));
198
199 params.videoUrl = static_cast<jstring>(env->GetObjectField(
200 playbackParams, gFieldIds.videoUrl));
201 }
202
Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(JNIEnv * env,jclass clazz,jbyteArray uuid,jstring name,jobject outValue)203 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(
204 JNIEnv* env, jclass clazz, jbyteArray uuid,
205 jstring name, jobject outValue) {
206
207 if (NULL == uuid || NULL == name || NULL == outValue) {
208 jniThrowException(env, "java/lang/NullPointerException",
209 "One or more null input parameters");
210 return JNI_FALSE;
211 }
212
213 Uuid juuid = jbyteArrayToUuid(env, uuid);
214 if (!isUuidSizeValid(juuid)) {
215 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
216 "invalid UUID size, expected %u bytes", kUuidSize);
217 return JNI_FALSE;
218 }
219
220 AMediaObjects aMediaObjects;
221 aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
222 if (NULL == aMediaObjects.getDrm()) {
223 jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
224 return JNI_FALSE;
225 }
226
227 const char *utf8_name = env->GetStringUTFChars(name, NULL);
228 const char *utf8_outValue = NULL;
229 media_status_t status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
230 utf8_name, &utf8_outValue);
231 env->ReleaseStringUTFChars(name, utf8_name);
232
233 if (NULL != utf8_outValue) {
234 clazz = env->GetObjectClass(outValue);
235 jmethodID mId = env->GetMethodID (clazz, "append",
236 "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
237 jstring outString = env->NewStringUTF(
238 static_cast<const char *>(utf8_outValue));
239 env->CallObjectMethod(outValue, mId, outString);
240 } else {
241 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
242 "get property string returns %d", status);
243 return JNI_FALSE;
244 }
245 return JNI_TRUE;
246 }
247
Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(JNIEnv * env,jclass,jbyteArray uuid)248 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(
249 JNIEnv* env, jclass /* clazz */, jbyteArray uuid) {
250
251 if (NULL == uuid) {
252 jniThrowException(env, "java/lang/NullPointerException",
253 "uuid is NULL");
254 return JNI_FALSE;
255 }
256
257 Uuid juuid = jbyteArrayToUuid(env, uuid);
258 if (!isUuidSizeValid(juuid)) {
259 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
260 "invalid UUID size, expected %u bytes", kUuidSize);
261 return JNI_FALSE;
262 }
263
264 AMediaDrm* drm = AMediaDrm_createByUUID(&juuid[0]);
265 const char *propertyName = "clientId";
266 const uint8_t value[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
267 media_status_t status = AMediaDrm_setPropertyByteArray(drm, propertyName, value, sizeof(value));
268 if (status != AMEDIA_OK) {
269 jniThrowException(env, "java/lang/RuntimeException", "setPropertyByteArray failed");
270 AMediaDrm_release(drm);
271 return JNI_FALSE;
272 }
273 AMediaDrmByteArray array;
274 status = AMediaDrm_getPropertyByteArray(drm, propertyName, &array);
275 if (status != AMEDIA_OK) {
276 jniThrowException(env, "java/lang/RuntimeException", "getPropertyByteArray failed");
277 AMediaDrm_release(drm);
278 return JNI_FALSE;
279 }
280 if (array.length != sizeof(value)) {
281 jniThrowException(env, "java/lang/RuntimeException", "byte array size differs");
282 AMediaDrm_release(drm);
283 return JNI_FALSE;
284 }
285 if (!array.ptr) {
286 jniThrowException(env, "java/lang/RuntimeException", "byte array pointer is null");
287 AMediaDrm_release(drm);
288 return JNI_FALSE;
289 }
290 if (memcmp(array.ptr, value, sizeof(value)) != 0) {
291 jniThrowException(env, "java/lang/RuntimeException", "byte array content differs");
292 AMediaDrm_release(drm);
293 return JNI_FALSE;
294 }
295 AMediaDrm_release(drm);
296 return JNI_TRUE;
297 }
298
Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(JNIEnv * env,jclass,jbyteArray uuid,jstring videoUrl)299 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(
300 JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
301
302 if (NULL == uuid || NULL == videoUrl) {
303 jniThrowException(env, "java/lang/NullPointerException",
304 "null uuid or null videoUrl");
305 return JNI_FALSE;
306 }
307
308 Uuid juuid = jbyteArrayToUuid(env, uuid);
309 if (!isUuidSizeValid(juuid)) {
310 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
311 "invalid UUID size, expected %u bytes", kUuidSize);
312 return JNI_FALSE;
313 }
314
315 AMediaObjects aMediaObjects;
316 aMediaObjects.setVideoExtractor(AMediaExtractor_new());
317 const char* url = env->GetStringUTFChars(videoUrl, 0);
318 if (url) {
319 media_status_t status = setDataSourceFdOrUrl(
320 aMediaObjects.getVideoExtractor(), url);
321 env->ReleaseStringUTFChars(videoUrl, url);
322
323 if (status != AMEDIA_OK) {
324 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
325 "set video data source error=%d", status);
326 return JNI_FALSE;
327 }
328 }
329
330 PsshInfo* psshInfo = AMediaExtractor_getPsshInfo(aMediaObjects.getVideoExtractor());
331 if (psshInfo == NULL) {
332 jniThrowException(env, "java/lang/RuntimeException", "null psshInfo");
333 return JNI_FALSE;
334 }
335
336 jboolean testResult = JNI_FALSE;
337 for (size_t i = 0; i < psshInfo->numentries; i++) {
338 PsshEntry *entry = &psshInfo->entries[i];
339
340 if (0 == memcmp(entry->uuid, kClearKeyUuid, sizeof(entry->uuid))) {
341 aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
342 if (aMediaObjects.getDrm()) {
343 testResult = JNI_TRUE;
344 } else {
345 ALOGE("Failed to create media drm=%zd", i);
346 testResult = JNI_FALSE;
347 }
348 break;
349 }
350 }
351 return testResult;
352 }
353
isVideo(const char * mime)354 static bool isVideo(const char* mime) {
355 return !strncmp(mime, "video/", 6) ? true : false;
356 }
357
isAudio(const char * mime)358 static bool isAudio(const char* mime) {
359 return !strncmp(mime, "audio/", 6) ? true : false;
360 }
361
addTrack(const AMediaFormat * format,const char * mime,const AMediaCrypto * crypto,const ANativeWindow * window,AMediaCodec ** codec)362 static void addTrack(const AMediaFormat* format,
363 const char* mime, const AMediaCrypto* crypto,
364 const ANativeWindow* window, AMediaCodec** codec) {
365
366 *codec = AMediaCodec_createDecoderByType(mime);
367 if (codec == NULL) {
368 ALOGE("cannot create codec for %s", mime);
369 return;
370 }
371
372 AMediaCodec_configure(*codec, format,
373 const_cast<ANativeWindow*>(window),
374 const_cast<AMediaCrypto*>(crypto), 0);
375 }
376
addTracks(const AMediaExtractor * extractor,const AMediaCrypto * crypto,const ANativeWindow * window,AMediaCodec ** codec)377 static void addTracks(const AMediaExtractor* extractor,
378 const AMediaCrypto* crypto, const ANativeWindow* window,
379 AMediaCodec** codec) {
380 size_t numTracks = AMediaExtractor_getTrackCount(
381 const_cast<AMediaExtractor*>(extractor));
382
383 AMediaFormat* trackFormat = NULL;
384 for (size_t i = 0; i < numTracks; ++i) {
385 trackFormat = AMediaExtractor_getTrackFormat(
386 const_cast<AMediaExtractor*>(extractor), i);
387 if (trackFormat) {
388 ALOGV("track %zd format: %s", i,
389 AMediaFormat_toString(trackFormat));
390
391 const char* mime = "";
392 if (!AMediaFormat_getString(
393 trackFormat, AMEDIAFORMAT_KEY_MIME, &mime)) {
394 ALOGE("no mime type");
395
396 AMediaFormat_delete(trackFormat);
397 return;
398 } else if (isAudio(mime) || isVideo(mime)) {
399 AMediaExtractor_selectTrack(
400 const_cast<AMediaExtractor*>(extractor), i);
401 ALOGV("track %zd codec format: %s", i,
402 AMediaFormat_toString(trackFormat));
403
404 addTrack(trackFormat, mime, crypto, window, codec);
405 AMediaCodec_start(*codec);
406 AMediaCodec_flush(*codec);
407 AMediaExtractor_seekTo(
408 const_cast<AMediaExtractor*>(extractor), 0,
409 AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
410 }
411 AMediaFormat_delete(trackFormat);
412 }
413 }
414 }
415
getSystemNanoTime()416 static int64_t getSystemNanoTime() {
417 timespec now;
418 clock_gettime(CLOCK_MONOTONIC, &now);
419 return now.tv_sec * 1000000000LL + now.tv_nsec;
420 }
421
fillDecoder(AMediaCodec * codec,AMediaExtractor * extractor,int64_t * presentationTimeUs,bool * eosReached)422 static void fillDecoder(AMediaCodec* codec, AMediaExtractor* extractor,
423 int64_t* presentationTimeUs, bool* eosReached) {
424 media_status_t status = AMEDIA_OK;
425
426 ssize_t bufferIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
427 if (bufferIndex >= 0) {
428 size_t bufsize;
429 uint8_t* buf = AMediaCodec_getInputBuffer(codec, bufferIndex, &bufsize);
430
431 int sampleSize = AMediaExtractor_readSampleData(extractor, buf, bufsize);
432 if (sampleSize < 0) {
433 sampleSize = 0;
434 *eosReached = true;
435 }
436
437 *presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
438
439 AMediaCodecCryptoInfo *cryptoInfo =
440 AMediaExtractor_getSampleCryptoInfo(extractor);
441
442 if (cryptoInfo) {
443 status = AMediaCodec_queueSecureInputBuffer(
444 codec, bufferIndex, 0, cryptoInfo,
445 *presentationTimeUs,
446 *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
447 AMediaCodecCryptoInfo_delete(cryptoInfo);
448 } else {
449 status = AMediaCodec_queueInputBuffer(
450 codec, bufferIndex, 0, sampleSize,
451 *presentationTimeUs,
452 *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
453 }
454 AMediaExtractor_advance(extractor);
455 }
456 }
457
drainDecoder(AMediaCodec * codec,int64_t presentationTimeUs,int64_t * startTimeNano)458 static bool drainDecoder(AMediaCodec* codec, int64_t presentationTimeUs,
459 int64_t* startTimeNano) {
460
461 AMediaCodecBufferInfo info;
462 ssize_t bufferIndex = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
463 if (bufferIndex >= 0) {
464 if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
465 return true; // eos reached
466 }
467
468 if (*startTimeNano < 0) {
469 *startTimeNano = getSystemNanoTime() - (presentationTimeUs * 1000);
470 }
471 int64_t delay = (*startTimeNano + presentationTimeUs * 1000) -
472 getSystemNanoTime();
473 if (delay > 0) {
474 usleep(delay / 1000);
475 }
476
477 AMediaCodec_releaseOutputBuffer(codec, bufferIndex, info.size != 0);
478 } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
479 ALOGV("output buffers changed");
480 } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
481 AMediaFormat* format = AMediaCodec_getOutputFormat(codec);
482 ALOGV("format changed to: %s", AMediaFormat_toString(format));
483 AMediaFormat_delete(format);
484 } else if (bufferIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
485 ALOGV("no output buffer right now");
486 usleep(20000);
487 } else {
488 ALOGV("unexpected info code: %zd", bufferIndex);
489 }
490 return false;
491 }
492
playContent(JNIEnv * env,const AMediaObjects & aMediaObjects,PlaybackParams & params,const AMediaDrmSessionId & sessionId,Uuid uuid)493 static jboolean playContent(JNIEnv* env, const AMediaObjects& aMediaObjects,
494 PlaybackParams& params, const AMediaDrmSessionId& sessionId, Uuid uuid) {
495
496 ANativeWindow *window = ANativeWindow_fromSurface(env, params.surface);
497 AMediaExtractor* audioExtractor = aMediaObjects.getAudioExtractor();
498 AMediaExtractor* videoExtractor = aMediaObjects.getVideoExtractor();
499
500 AMediaCodec* audioCodec = NULL;
501 AMediaCodec* videoCodec = NULL;
502 AMediaCrypto* crypto = NULL;
503
504 crypto = AMediaCrypto_new(&uuid[0], sessionId.ptr, sessionId.length);
505 if (crypto == NULL) {
506 jniThrowException(env, "java/lang/RuntimeException",
507 "failed to create crypto object");
508 return JNI_FALSE;
509 }
510
511 addTracks(audioExtractor, NULL, NULL, &audioCodec);
512
513 addTracks(videoExtractor, crypto, window, &videoCodec);
514
515 bool sawAudioInputEos = false;
516 bool sawAudioOutputEos = false;
517 bool sawVideoInputEos = false;
518 bool sawVideoOutputEos = false;
519 int64_t videoPresentationTimeUs = 0;
520 int64_t videoStartTimeNano = -1;
521 struct timespec timeSpec;
522 clock_gettime(CLOCK_MONOTONIC, &timeSpec);
523 time_t startTimeSec = timeSpec.tv_sec;
524
525 while (!sawAudioOutputEos && !sawVideoOutputEos) {
526 if (!sawVideoInputEos) {
527 fillDecoder(videoCodec, videoExtractor,
528 &videoPresentationTimeUs, &sawVideoInputEos);
529 }
530
531 if (!sawAudioInputEos) {
532 // skip audio, still need to advance the audio extractor
533 AMediaExtractor_advance(audioExtractor);
534 }
535
536 if (!sawVideoOutputEos) {
537 sawVideoOutputEos = drainDecoder(videoCodec, videoPresentationTimeUs,
538 &videoStartTimeNano);
539 }
540
541 clock_gettime(CLOCK_MONOTONIC, &timeSpec);
542 if (timeSpec.tv_sec >= static_cast<time_t>(
543 (startTimeSec + kPlayTimeSeconds))) {
544 // stop reading samples and drain the output buffers
545 sawAudioInputEos = sawVideoInputEos = true;
546 sawAudioOutputEos = true; // ignore audio
547 }
548 }
549
550 if (audioCodec) {
551 AMediaCodec_stop(audioCodec);
552 AMediaCodec_delete(audioCodec);
553 }
554 if (videoCodec) {
555 AMediaCodec_stop(videoCodec);
556 AMediaCodec_delete(videoCodec);
557 }
558
559 AMediaCrypto_delete(crypto);
560 ANativeWindow_release(window);
561 return JNI_TRUE;
562 }
563
listener(AMediaDrm *,const AMediaDrmSessionId *,AMediaDrmEventType eventType,int,const uint8_t *,size_t)564 static void listener(
565 AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
566 AMediaDrmEventType eventType,
567 int /*extra*/, const uint8_t* /*data*/, size_t /*dataSize*/) {
568
569 switch (eventType) {
570 case EVENT_PROVISION_REQUIRED:
571 ALOGD("EVENT_PROVISION_REQUIRED received");
572 break;
573 case EVENT_KEY_REQUIRED:
574 ALOGD("EVENT_KEY_REQUIRED received");
575 break;
576 case EVENT_KEY_EXPIRED:
577 ALOGD("EVENT_KEY_EXPIRED received");
578 break;
579 case EVENT_VENDOR_DEFINED:
580 gGotVendorDefinedEvent = true;
581 ALOGD("EVENT_VENDOR_DEFINED received");
582 break;
583 case EVENT_SESSION_RECLAIMED:
584 ALOGD("EVENT_SESSION_RECLAIMED received");
585 break;
586 default:
587 ALOGD("Unknown event received");
588 break;
589 }
590 }
591
onExpirationUpdateListener(AMediaDrm *,const AMediaDrmSessionId *,int64_t expiryTimeInMS)592 static void onExpirationUpdateListener(
593 AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
594 int64_t expiryTimeInMS) {
595
596 if (expiryTimeInMS == 100) {
597 ALOGD("Updates new expiration time to %" PRId64 " ms", expiryTimeInMS);
598 gListenerGotValidExpiryTime = true;
599 } else {
600 ALOGE("Expects 100 ms for expiry time, received: %" PRId64 " ms", expiryTimeInMS);
601 gListenerGotValidExpiryTime = false;
602 }
603 }
604
onKeysChangeListener(AMediaDrm *,const AMediaDrmSessionId *,const AMediaDrmKeyStatus * keysStatus,size_t numKeys,bool hasNewUsableKey)605 static void onKeysChangeListener(
606 AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
607 const AMediaDrmKeyStatus* keysStatus, size_t numKeys, bool hasNewUsableKey) {
608
609 gOnKeyChangeListenerOK = false;
610 if (numKeys != 3) {
611 ALOGE("Expects 3 keys, received %zd keys", numKeys);
612 return;
613 }
614
615 if (!hasNewUsableKey) {
616 ALOGE("Expects hasNewUsableKey to be true");
617 return;
618 }
619
620 ALOGD("Number of keys changed=%zd", numKeys);
621 AMediaDrmKeyStatus keyStatus;
622 for (size_t i = 0; i < numKeys; ++i) {
623 keyStatus.keyId.ptr = keysStatus[i].keyId.ptr;
624 keyStatus.keyId.length = keysStatus[i].keyId.length;
625 keyStatus.keyType = keysStatus[i].keyType;
626
627 ALOGD("key[%zd]: key: %0x, %0x, %0x", i, keyStatus.keyId.ptr[0], keyStatus.keyId.ptr[1],
628 keyStatus.keyId.ptr[2]);
629 ALOGD("key[%zd]: key type=%d", i, keyStatus.keyType);
630 }
631 gOnKeyChangeListenerOK = true;
632 }
633
acquireLicense(JNIEnv * env,const AMediaObjects & aMediaObjects,const AMediaDrmSessionId & sessionId,AMediaDrmKeyType keyType)634 static void acquireLicense(
635 JNIEnv* env, const AMediaObjects& aMediaObjects, const AMediaDrmSessionId& sessionId,
636 AMediaDrmKeyType keyType) {
637 // Pointer to keyRequest memory, which remains until the next
638 // AMediaDrm_getKeyRequest call or until the drm object is released.
639 const uint8_t* keyRequest;
640 size_t keyRequestSize = 0;
641 std::string errorMessage;
642
643 // The server recognizes "video/mp4" but not "video/avc".
644 media_status_t status = AMediaDrm_getKeyRequest(aMediaObjects.getDrm(),
645 &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
646 "video/mp4" /*mimeType*/, keyType,
647 NULL, 0, &keyRequest, &keyRequestSize);
648 if (status != AMEDIA_OK) {
649 errorMessage.assign("getKeyRequest failed, error = %d");
650 goto errorOut;
651 }
652
653 if (kKeyRequestSize != keyRequestSize) {
654 ALOGE("Invalid keyRequestSize %zd", kKeyRequestSize);
655 errorMessage.assign("Invalid key request size, error = %d");
656 status = AMEDIA_DRM_NEED_KEY;
657 goto errorOut;
658 }
659
660 if (memcmp(kKeyRequestData, keyRequest, kKeyRequestSize) != 0) {
661 errorMessage.assign("Invalid key request data is returned, error = %d");
662 status = AMEDIA_DRM_NEED_KEY;
663 goto errorOut;
664 }
665
666 AMediaDrmKeySetId keySetId;
667 gGotVendorDefinedEvent = false;
668 gListenerGotValidExpiryTime = false;
669 gOnKeyChangeListenerOK = false;
670 status = AMediaDrm_provideKeyResponse(aMediaObjects.getDrm(), &sessionId,
671 reinterpret_cast<const uint8_t*>(kResponse),
672 sizeof(kResponse), &keySetId);
673 if (status == AMEDIA_OK) {
674 return; // success
675 }
676
677 errorMessage.assign("provideKeyResponse failed, error = %d");
678
679 errorOut:
680 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
681 jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), status);
682 }
683
Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(JNIEnv * env,jclass,jbyteArray uuid,jobject playbackParams)684 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(
685 JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
686 if (NULL == uuid || NULL == playbackParams) {
687 jniThrowException(env, "java/lang/NullPointerException",
688 "null uuid or null playback parameters");
689 return JNI_FALSE;
690 }
691
692 Uuid juuid = jbyteArrayToUuid(env, uuid);
693 if (!isUuidSizeValid(juuid)) {
694 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
695 "invalid UUID size, expected %u bytes", kUuidSize);
696 return JNI_FALSE;
697 }
698
699 PlaybackParams params;
700 initPlaybackParams(env, playbackParams, params);
701
702 AMediaObjects aMediaObjects;
703 media_status_t status = AMEDIA_OK;
704 aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
705 if (NULL == aMediaObjects.getDrm()) {
706 jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
707 return JNI_FALSE;
708 }
709
710 status = AMediaDrm_setOnEventListener(aMediaObjects.getDrm(), listener);
711 if (status != AMEDIA_OK) {
712 jniThrowException(env, "java/lang/RuntimeException",
713 "setOnEventListener failed");
714 return JNI_FALSE;
715 }
716
717 status = AMediaDrm_setOnExpirationUpdateListener(aMediaObjects.getDrm(),
718 onExpirationUpdateListener);
719 if (status != AMEDIA_OK) {
720 jniThrowException(env, "java/lang/RuntimeException",
721 "setOnExpirationUpdateListener failed");
722 return JNI_FALSE;
723 }
724
725 status = AMediaDrm_setOnKeysChangeListener(aMediaObjects.getDrm(),
726 onKeysChangeListener);
727 if (status != AMEDIA_OK) {
728 jniThrowException(env, "java/lang/RuntimeException",
729 "setOnKeysChangeListener failed");
730 return JNI_FALSE;
731 }
732
733 aMediaObjects.setAudioExtractor(AMediaExtractor_new());
734 const char* url = env->GetStringUTFChars(params.audioUrl, 0);
735 if (url) {
736 status = setDataSourceFdOrUrl(
737 aMediaObjects.getAudioExtractor(), url);
738 env->ReleaseStringUTFChars(params.audioUrl, url);
739
740 if (status != AMEDIA_OK) {
741 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
742 "set audio data source error=%d", status);
743 return JNI_FALSE;
744 }
745 }
746
747 aMediaObjects.setVideoExtractor(AMediaExtractor_new());
748 url = env->GetStringUTFChars(params.videoUrl, 0);
749 if (url) {
750 status = setDataSourceFdOrUrl(
751 aMediaObjects.getVideoExtractor(), url);
752 env->ReleaseStringUTFChars(params.videoUrl, url);
753
754 if (status != AMEDIA_OK) {
755 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
756 "set video data source error=%d", status);
757 return JNI_FALSE;
758 }
759 }
760
761 AMediaDrmSessionId sessionId;
762 status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
763 if (status != AMEDIA_OK) {
764 jniThrowException(env, "java/lang/RuntimeException",
765 "openSession failed");
766 return JNI_FALSE;
767 }
768
769 acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
770
771 // Checks if the event listener has received the expected event sent by
772 // provideKeyResponse. This is for testing AMediaDrm_setOnEventListener().
773 const char *utf8_outValue = NULL;
774 status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
775 "listenerTestSupport", &utf8_outValue);
776 if (status == AMEDIA_OK && NULL != utf8_outValue) {
777 std::string eventType(utf8_outValue);
778 if (eventType.compare("true") == 0) {
779 int count = 0;
780 while ((!gGotVendorDefinedEvent ||
781 !gListenerGotValidExpiryTime ||
782 !gOnKeyChangeListenerOK) && count++ < 5) {
783 // Prevents race condition when the event arrives late
784 usleep(10000);
785 }
786
787 if (!gGotVendorDefinedEvent) {
788 ALOGE("Event listener did not receive the expected event.");
789 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
790 "Event listener did not receive the expected event.");
791 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
792 return JNI_FALSE;
793 }
794
795 // Checks if onExpirationUpdateListener received the correct expiry time.
796 if (!gListenerGotValidExpiryTime) {
797 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
798 "onExpirationUpdateListener received incorrect expiry time.");
799 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
800 return JNI_FALSE;
801 }
802
803 // Checks if onKeysChangeListener succeeded.
804 if (!gOnKeyChangeListenerOK) {
805 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
806 "onKeysChangeListener failed");
807 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
808 return JNI_FALSE;
809 }
810 }
811 }
812
813 playContent(env, aMediaObjects, params, sessionId, juuid);
814
815 status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
816 if (status != AMEDIA_OK) {
817 jniThrowException(env, "java/lang/RuntimeException",
818 "closeSession failed");
819 return JNI_FALSE;
820 }
821 return JNI_TRUE;
822 }
823
Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(JNIEnv * env,jclass,jbyteArray uuid)824 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(
825 JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
826
827 if (NULL == uuid) {
828 jniThrowException(env, "java/lang/NullPointerException", "null uuid");
829 return JNI_FALSE;
830 }
831
832 Uuid juuid = jbyteArrayToUuid(env, uuid);
833 if (!isUuidSizeValid(juuid)) {
834 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
835 "invalid UUID size, expected %u bytes", kUuidSize);
836 return JNI_FALSE;
837 }
838
839 AMediaObjects aMediaObjects;
840 media_status_t status = AMEDIA_OK;
841 aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
842 if (NULL == aMediaObjects.getDrm()) {
843 jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
844 return JNI_FALSE;
845 }
846
847 AMediaDrmSessionId sessionId;
848 status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
849 if (status != AMEDIA_OK) {
850 jniThrowException(env, "java/lang/RuntimeException",
851 "openSession failed");
852 return JNI_FALSE;
853 }
854
855 size_t numPairs = 3;
856 AMediaDrmKeyValue keyStatus[numPairs];
857
858 // Test default key status, should return zero key status
859 status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
860 if (status != AMEDIA_OK) {
861 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
862 "AMediaDrm_queryKeyStatus failed, error = %d", status);
863 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
864 return JNI_FALSE;
865 }
866
867 if (numPairs != 0) {
868 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
869 "AMediaDrm_queryKeyStatus failed, no policy should be defined");
870 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
871 return JNI_FALSE;
872 }
873
874 acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
875
876 // Test short buffer
877 numPairs = 2;
878 status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
879 if (status != AMEDIA_DRM_SHORT_BUFFER) {
880 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
881 "AMediaDrm_queryKeyStatus should return AMEDIA_DRM_SHORT_BUFFER, error = %d",
882 status);
883 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
884 return JNI_FALSE;
885 }
886
887 // Test valid key status
888 numPairs = 3;
889 status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
890 if (status != AMEDIA_OK) {
891 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
892 "AMediaDrm_queryKeyStatus failed, error = %d", status);
893 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
894 return JNI_FALSE;
895 }
896
897 for (size_t i = 0; i < numPairs; ++i) {
898 ALOGI("AMediaDrm_queryKeyStatus: key=%s, value=%s", keyStatus[i].mKey, keyStatus[i].mValue);
899 }
900
901 if (numPairs != 3) {
902 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
903 "AMediaDrm_queryKeyStatus returns %zd key status, expecting 3", numPairs);
904 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
905 return JNI_FALSE;
906 }
907
908 status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
909 if (status != AMEDIA_OK) {
910 jniThrowException(env, "java/lang/RuntimeException",
911 "closeSession failed");
912 return JNI_FALSE;
913 }
914 return JNI_TRUE;
915 }
916
Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative(JNIEnv * env,jclass,jbyteArray uuid)917 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative(
918 JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
919
920 if (NULL == uuid) {
921 jniThrowException(env, "java/lang/NullPointerException", "null uuid");
922 return JNI_FALSE;
923 }
924
925 Uuid juuid = jbyteArrayToUuid(env, uuid);
926 if (!isUuidSizeValid(juuid)) {
927 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
928 "invalid UUID size, expected %u bytes", kUuidSize);
929 return JNI_FALSE;
930 }
931
932 AMediaObjects aMediaObjects;
933 media_status_t status = AMEDIA_OK;
934 aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
935 if (NULL == aMediaObjects.getDrm()) {
936 jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
937 return JNI_FALSE;
938 }
939
940 // Stores duplicates of session id.
941 std::vector<std::vector<uint8_t> > sids;
942
943 std::list<AMediaDrmSessionId> sessionIds;
944 AMediaDrmSessionId sessionId;
945 for (int i = 0; i < 5; ++i) {
946 status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
947 if (status != AMEDIA_OK) {
948 jniThrowException(env, "java/lang/RuntimeException", "openSession failed");
949 return JNI_FALSE;
950 }
951
952 // Allocates a new pointer to duplicate the session id returned by
953 // AMediaDrm_openSession. These new pointers will be passed to
954 // AMediaDrm_closeSession, which verifies that the ndk
955 // can find the session id even if the pointer has changed.
956 sids.push_back(std::vector<uint8_t>(sessionId.length));
957 memcpy(sids.at(i).data(), sessionId.ptr, sessionId.length);
958 sessionId.ptr = static_cast<uint8_t *>(sids.at(i).data());
959 sessionIds.push_back(sessionId);
960 }
961
962 for (auto sessionId : sessionIds) {
963 status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
964 if (status != AMEDIA_OK) {
965 jniThrowException(env, "java/lang/RuntimeException", "closeSession failed");
966 return JNI_FALSE;
967 }
968 }
969
970 return JNI_TRUE;
971 }
972
973 static JNINativeMethod gMethods[] = {
974 { "isCryptoSchemeSupportedNative", "([B)Z",
975 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
976
977 { "testClearKeyPlaybackNative",
978 "([BLandroid/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
979 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative },
980
981 { "testGetPropertyStringNative",
982 "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
983 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative },
984
985 { "testPropertyByteArrayNative",
986 "([B)Z",
987 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative },
988
989 { "testPsshNative", "([BLjava/lang/String;)Z",
990 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative },
991
992 { "testQueryKeyStatusNative", "([B)Z",
993 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative },
994
995 { "testFindSessionIdNative", "([B)Z",
996 (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative },
997 };
998
register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv * env)999 int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
1000 jint result = JNI_ERR;
1001 jclass testClass =
1002 env->FindClass("android/media/cts/NativeMediaDrmClearkeyTest");
1003 if (testClass) {
1004 jclass playbackParamsClass = env->FindClass(
1005 "android/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams");
1006 if (playbackParamsClass) {
1007 jclass surfaceClass =
1008 env->FindClass("android/view/Surface");
1009 if (surfaceClass) {
1010 gFieldIds.surface = env->GetFieldID(playbackParamsClass,
1011 "surface", "Landroid/view/Surface;");
1012 } else {
1013 gFieldIds.surface = NULL;
1014 }
1015 gFieldIds.mimeType = env->GetFieldID(playbackParamsClass,
1016 "mimeType", "Ljava/lang/String;");
1017 gFieldIds.audioUrl = env->GetFieldID(playbackParamsClass,
1018 "audioUrl", "Ljava/lang/String;");
1019 gFieldIds.videoUrl = env->GetFieldID(playbackParamsClass,
1020 "videoUrl", "Ljava/lang/String;");
1021 } else {
1022 ALOGE("PlaybackParams class not found");
1023 }
1024
1025 } else {
1026 ALOGE("NativeMediaDrmClearkeyTest class not found");
1027 }
1028
1029 result = env->RegisterNatives(testClass, gMethods,
1030 sizeof(gMethods) / sizeof(JNINativeMethod));
1031 return result;
1032 }
1033