Modular DRM for MediaPlayer
Bug: 34559906
Test: Manual through the test app
Change-Id: I286f9ff199c34563b7b8643de725f8d1534ea06c
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDrm.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDrm.cpp
new file mode 100644
index 0000000..cb9a5f8
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDrm.cpp
@@ -0,0 +1,366 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NuPlayerDrm"
+
+#include "NuPlayerDrm.h"
+
+#include <binder/IServiceManager.h>
+#include <media/IMediaDrmService.h>
+#include <utils/Log.h>
+
+
+namespace android {
+
+// static helpers - internal
+
+sp<IDrm> NuPlayerDrm::CreateDrm(status_t *pstatus)
+{
+ status_t &status = *pstatus;
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.drm"));
+ ALOGV("CreateDrm binder %p", (binder != NULL ? binder.get() : 0));
+
+ sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder);
+ if (service == NULL) {
+ ALOGE("CreateDrm failed at IMediaDrmService");
+ return NULL;
+ }
+
+ sp<IDrm> drm = service->makeDrm();
+ if (drm == NULL) {
+ ALOGE("CreateDrm failed at makeDrm");
+ return NULL;
+ }
+
+ // this is before plugin creation so NO_INIT is fine
+ status = drm->initCheck();
+ if (status != OK && status != NO_INIT) {
+ ALOGE("CreateDrm failed drm->initCheck(): %d", status);
+ return NULL;
+ }
+ return drm;
+}
+
+sp<ICrypto> NuPlayerDrm::createCrypto(status_t *pstatus)
+{
+ status_t &status = *pstatus;
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.drm"));
+
+ sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder);
+ if (service == NULL) {
+ status = UNKNOWN_ERROR;
+ ALOGE("CreateCrypto failed at IMediaDrmService");
+ return NULL;
+ }
+
+ sp<ICrypto> crypto = service->makeCrypto();
+ if (crypto == NULL) {
+ status = UNKNOWN_ERROR;
+ ALOGE("createCrypto failed");
+ return NULL;
+ }
+
+ // this is before plugin creation so NO_INIT is fine
+ status = crypto->initCheck();
+ if (status != OK && status != NO_INIT) {
+ ALOGE("createCrypto failed crypto->initCheck(): %d", status);
+ return NULL;
+ }
+
+ return crypto;
+}
+
+Vector<DrmUUID> NuPlayerDrm::parsePSSH(const void *pssh, size_t psshsize)
+{
+ Vector<DrmUUID> drmSchemes, empty;
+ const int DATALEN_SIZE = 4;
+
+ // the format of the buffer is 1 or more of:
+ // {
+ // 16 byte uuid
+ // 4 byte data length N
+ // N bytes of data
+ // }
+ // Determine the number of entries in the source data.
+ // Since we got the data from stagefright, we trust it is valid and properly formatted.
+
+ const uint8_t *data = (const uint8_t*)pssh;
+ size_t len = psshsize;
+ size_t numentries = 0;
+ while (len > 0) {
+ if (len < DrmUUID::UUID_SIZE) {
+ ALOGE("ParsePSSH: invalid PSSH data");
+ return empty;
+ }
+
+ const uint8_t *uuidPtr = data;
+
+ // skip uuid
+ data += DrmUUID::UUID_SIZE;
+ len -= DrmUUID::UUID_SIZE;
+
+ // get data length
+ if (len < DATALEN_SIZE) {
+ ALOGE("ParsePSSH: invalid PSSH data");
+ return empty;
+ }
+
+ uint32_t datalen = *((uint32_t*)data);
+ data += DATALEN_SIZE;
+ len -= DATALEN_SIZE;
+
+ if (len < datalen) {
+ ALOGE("ParsePSSH: invalid PSSH data");
+ return empty;
+ }
+
+ // skip the data
+ data += datalen;
+ len -= datalen;
+
+ DrmUUID _uuid(uuidPtr);
+ drmSchemes.add(_uuid);
+
+ ALOGV("ParsePSSH[%zu]: %s: %s", numentries,
+ _uuid.toHexString().string(),
+ DrmUUID::arrayToHex(data, datalen).string()
+ );
+
+ numentries++;
+ }
+
+ return drmSchemes;
+}
+
+Vector<DrmUUID> NuPlayerDrm::getSupportedDrmSchemes(const void *pssh, size_t psshsize)
+{
+ Vector<DrmUUID> psshDRMs = parsePSSH(pssh, psshsize);
+
+ Vector<DrmUUID> supportedDRMs;
+ // temporary DRM object for crypto Scheme enquiry (without creating a plugin)
+ status_t status = OK;
+ sp<IDrm> drm = CreateDrm(&status);
+ if (drm != NULL) {
+ for (size_t i = 0; i < psshDRMs.size(); i++) {
+ DrmUUID uuid = psshDRMs[i];
+ if (drm->isCryptoSchemeSupported(uuid.ptr(), String8()))
+ supportedDRMs.add(uuid);
+ }
+
+ drm.clear();
+ } else {
+ ALOGE("getSupportedDrmSchemes: Can't create Drm obj: %d", status);
+ }
+
+ ALOGV("getSupportedDrmSchemes: psshDRMs: %zu supportedDRMs: %zu",
+ psshDRMs.size(), supportedDRMs.size());
+
+ return supportedDRMs;
+}
+
+// static helpers - public
+
+sp<ICrypto> NuPlayerDrm::createCryptoAndPlugin(const uint8_t uuid[16],
+ const Vector<uint8_t> &drmSessionId, status_t &status)
+{
+ // Extra check
+ if (drmSessionId.isEmpty()) {
+ status = INVALID_OPERATION;
+ ALOGE("createCryptoAndPlugin: Failed. Empty drmSessionId. status: %d", status);
+ return NULL;
+ }
+
+ status = OK;
+ sp<ICrypto> crypto = createCrypto(&status);
+ if (crypto == NULL) {
+ ALOGE("createCryptoAndPlugin: createCrypto failed. status: %d", status);
+ return NULL;
+ }
+ ALOGV("createCryptoAndPlugin: createCrypto succeeded");
+
+ status = crypto->createPlugin(uuid, drmSessionId.array(), drmSessionId.size());
+ if (status != OK) {
+ ALOGE("createCryptoAndPlugin: createCryptoPlugin failed. status: %d", status);
+ // crypto will clean itself when leaving the current scope
+ return NULL;
+ }
+
+ return crypto;
+}
+
+// Parcel has only private copy constructor so passing it in rather than returning
+void NuPlayerDrm::retrieveDrmInfo(const void *pssh, size_t psshsize,
+ const Vector<String8> &mimes_in, Parcel *parcel)
+{
+ // 0) Make mimes a vector of unique items while keeping the original order; video first
+ Vector<String8> mimes;
+ for (size_t j = 0; j < mimes_in.size(); j++) {
+ String8 mime = mimes_in[j];
+ bool exists = false;
+ for (size_t i = 0; i < mimes.size() && !exists; i++) {
+ if (mimes[i] == mime) {
+ exists = true;
+ }
+ } // for i
+
+ if (!exists) {
+ mimes.add(mime);
+ }
+ } // for j
+
+
+ // 1) PSSH bytes
+ parcel->writeUint32(psshsize);
+ parcel->writeByteArray(psshsize, (const uint8_t*)pssh);
+
+ ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO PSSH: size: %zu %s", psshsize,
+ DrmUUID::arrayToHex((uint8_t*)pssh, psshsize).string());
+
+ // 2) supportedDRMs
+ Vector<DrmUUID> supportedDRMs = getSupportedDrmSchemes(pssh, psshsize);
+ parcel->writeUint32(supportedDRMs.size());
+ for (size_t i = 0; i < supportedDRMs.size(); i++) {
+ DrmUUID uuid = supportedDRMs[i];
+ parcel->writeByteArray(DrmUUID::UUID_SIZE, uuid.ptr());
+
+ ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO supportedScheme[%zu] %s", i,
+ uuid.toHexString().string());
+ }
+
+ // TODO: remove mimes after it's removed from Java DrmInfo
+ // 3) mimes
+ parcel->writeUint32(mimes.size());
+ for (size_t i = 0; i < mimes.size(); i++) {
+ // writing as String16 so the Java framework side can unpack it to Java String
+ String16 mime(mimes[i]);
+ parcel->writeString16(mime);
+
+ ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO MIME[%zu] %s",
+ i, mimes[i].string());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+/// Helpers for NuPlayerDecoder
+////////////////////////////////////////////////////////////////////////////////////////////
+
+NuPlayerDrm::CryptoInfo *NuPlayerDrm::makeCryptoInfo(
+ int numSubSamples,
+ uint8_t key[kBlockSize],
+ uint8_t iv[kBlockSize],
+ CryptoPlugin::Mode mode,
+ size_t *clearbytes,
+ size_t *encryptedbytes)
+{
+ // size needed to store all the crypto data
+ size_t cryptosize = sizeof(CryptoInfo) +
+ sizeof(CryptoPlugin::SubSample) * numSubSamples;
+ CryptoInfo *ret = (CryptoInfo*) malloc(cryptosize);
+ if (ret == NULL) {
+ ALOGE("couldn't allocate %zu bytes", cryptosize);
+ return NULL;
+ }
+ ret->numSubSamples = numSubSamples;
+ memcpy(ret->key, key, kBlockSize);
+ memcpy(ret->iv, iv, kBlockSize);
+ ret->mode = mode;
+ ret->pattern.mEncryptBlocks = 0;
+ ret->pattern.mSkipBlocks = 0;
+ ret->subSamples = (CryptoPlugin::SubSample*)(ret + 1);
+ CryptoPlugin::SubSample *subSamples = ret->subSamples;
+
+ for (int i = 0; i < numSubSamples; i++) {
+ subSamples[i].mNumBytesOfClearData = (clearbytes == NULL) ? 0 : clearbytes[i];
+ subSamples[i].mNumBytesOfEncryptedData = (encryptedbytes == NULL) ?
+ 0 :
+ encryptedbytes[i];
+ }
+
+ return ret;
+}
+
+NuPlayerDrm::CryptoInfo *NuPlayerDrm::getSampleCryptoInfo(sp<MetaData> meta)
+{
+ uint32_t type;
+ const void *crypteddata;
+ size_t cryptedsize;
+
+ if (meta == NULL) {
+ ALOGE("getSampleCryptoInfo: Unexpected. No meta data for sample.");
+ return NULL;
+ }
+
+ if (!meta->findData(kKeyEncryptedSizes, &type, &crypteddata, &cryptedsize)) {
+ return NULL;
+ }
+ size_t numSubSamples = cryptedsize / sizeof(size_t);
+
+ if (numSubSamples <= 0) {
+ ALOGE("getSampleCryptoInfo INVALID numSubSamples: %zu", numSubSamples);
+ return NULL;
+ }
+
+ const void *cleardata;
+ size_t clearsize;
+ if (meta->findData(kKeyPlainSizes, &type, &cleardata, &clearsize)) {
+ if (clearsize != cryptedsize) {
+ // The two must be of the same length.
+ ALOGE("getSampleCryptoInfo mismatch cryptedsize: %zu != clearsize: %zu",
+ cryptedsize, clearsize);
+ return NULL;
+ }
+ }
+
+ const void *key;
+ size_t keysize;
+ if (meta->findData(kKeyCryptoKey, &type, &key, &keysize)) {
+ if (keysize != kBlockSize) {
+ ALOGE("getSampleCryptoInfo Keys must be %zu bytes in length: %zu",
+ kBlockSize, keysize);
+ // Keys must be 16 bytes in length.
+ return NULL;
+ }
+ }
+
+ const void *iv;
+ size_t ivsize;
+ if (meta->findData(kKeyCryptoIV, &type, &iv, &ivsize)) {
+ if (ivsize != kBlockSize) {
+ ALOGE("getSampleCryptoInfo IV must be %zu bytes in length: %zu",
+ kBlockSize, ivsize);
+ // IVs must be 16 bytes in length.
+ return NULL;
+ }
+ }
+
+ int32_t mode;
+ if (!meta->findInt32(kKeyCryptoMode, &mode)) {
+ mode = CryptoPlugin::kMode_AES_CTR;
+ }
+
+ return makeCryptoInfo(numSubSamples,
+ (uint8_t*) key,
+ (uint8_t*) iv,
+ (CryptoPlugin::Mode)mode,
+ (size_t*) cleardata,
+ (size_t*) crypteddata);
+}
+
+} // namespace android
+