| /* |
| * 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, Parcel *parcel) |
| { |
| // 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()); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////// |
| /// 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 %d 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 %d 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 |
| |