Add Support for WebM Back
Adds support for WebM to the CDM. Decryption remains untouched, however the initialization data is passed differently for WebM. The previous version of this change broke playback for certain apps that were being allowed to pass invalid MIME types before this change was made. This version maintains backwards-compatiblity for these apps for now by rewriting their MIME types as "video/mp4". Merge of https://widevine-internal-review.googlesource.com/9225/ and https://widevine-internal-review.googlesource.com/9611/ from the Widevine cdm repo. Bug: 10638562 Change-Id: Ib37e838d08363f07b34b3a2e79a3f80a1f43e9ad
This commit is contained in:
@@ -149,22 +149,51 @@ status_t WVDrmPlugin::getKeyRequest(
|
||||
return android::ERROR_DRM_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
// Build PSSH box for PSSH data in initData.
|
||||
static const char psshPrefix[] = {
|
||||
0, 0, 0, 0, // Total size
|
||||
'p', 's', 's', 'h', // "PSSH"
|
||||
0, 0, 0, 0, // Flags - must be zero
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
0, 0, 0, 0 // Size of initData
|
||||
};
|
||||
CdmInitData psshBox(psshPrefix, sizeof(psshPrefix) / sizeof(uint8_t));
|
||||
psshBox.append(reinterpret_cast<const char*>(initData.array()),
|
||||
initData.size());
|
||||
uint32_t* psshBoxSize = reinterpret_cast<uint32_t*>(&psshBox[0]);
|
||||
uint32_t* initDataSize = reinterpret_cast<uint32_t*>(&psshBox[28]);
|
||||
*initDataSize = htonl(initData.size());
|
||||
*psshBoxSize = htonl(psshBox.size());
|
||||
string cdmMimeType = mimeType.string();
|
||||
|
||||
// Provide backwards-compatibility for versions of apps that pass the wrong
|
||||
// MIME type.
|
||||
// TODO(b/13731637) - Remove this.
|
||||
if (cdmMimeType == "") {
|
||||
ALOGW("ALERT: Empty MIME type is supported for backwards-compatibility "
|
||||
"only and may go away in the future. Switch to \"video/mp4\" to "
|
||||
"become compliant.");
|
||||
cdmMimeType = "video/mp4";
|
||||
} else if (cdmMimeType == "video/avc") {
|
||||
ALOGW("ALERT: MIME type \"video/avc\" is supported for backwards-"
|
||||
"compatibility only and may go away in the future. Switch to "
|
||||
"\"video/mp4\" to become compliant.");
|
||||
cdmMimeType = "video/mp4";
|
||||
}
|
||||
|
||||
CdmInitData processedInitData;
|
||||
if (cdmMimeType == wvcdm::ISO_BMFF_MIME_TYPE) {
|
||||
// For ISO-BMFF, we need to wrap the init data in a new PSSH header.
|
||||
static const char psshPrefix[] = {
|
||||
0, 0, 0, 0, // Total size
|
||||
'p', 's', 's', 'h', // "PSSH"
|
||||
0, 0, 0, 0, // Flags - must be zero
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
0, 0, 0, 0 // Size of initData
|
||||
};
|
||||
processedInitData.assign(psshPrefix, sizeof(psshPrefix) / sizeof(char));
|
||||
processedInitData.append(reinterpret_cast<const char*>(initData.array()),
|
||||
initData.size());
|
||||
const size_t kPsshBoxSizeLocation = 0;
|
||||
const size_t kInitDataSizeLocation =
|
||||
sizeof(psshPrefix) - sizeof(uint32_t);
|
||||
uint32_t psshBoxSize = htonl(processedInitData.size());
|
||||
uint32_t initDataSize = htonl(initData.size());
|
||||
memcpy(&processedInitData[kPsshBoxSizeLocation], &psshBoxSize,
|
||||
sizeof(uint32_t));
|
||||
memcpy(&processedInitData[kInitDataSizeLocation], &initDataSize,
|
||||
sizeof(uint32_t));
|
||||
} else {
|
||||
// For other formats, we can pass the init data through unmodified.
|
||||
processedInitData.assign(reinterpret_cast<const char*>(initData.array()),
|
||||
initData.size());
|
||||
}
|
||||
|
||||
CdmAppParameterMap cdmParameters;
|
||||
for (size_t i = 0; i < optionalParameters.size(); ++i) {
|
||||
@@ -179,11 +208,10 @@ status_t WVDrmPlugin::getKeyRequest(
|
||||
|
||||
CdmKeyMessage keyRequest;
|
||||
string cdmDefaultUrl;
|
||||
|
||||
CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmKeySetId,
|
||||
psshBox, cdmLicenseType,
|
||||
cdmParameters, &keyRequest,
|
||||
&cdmDefaultUrl);
|
||||
cdmMimeType, processedInitData,
|
||||
cdmLicenseType, cdmParameters,
|
||||
&keyRequest, &cdmDefaultUrl);
|
||||
|
||||
if (isCdmResponseTypeSuccess(res)) {
|
||||
defaultUrl.clear();
|
||||
|
||||
@@ -31,8 +31,9 @@ class MockCDM : public WvContentDecryptionModule {
|
||||
|
||||
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
|
||||
|
||||
MOCK_METHOD7(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
|
||||
MOCK_METHOD8(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
|
||||
const CdmKeySetId&,
|
||||
const std::string&,
|
||||
const CdmInitData&,
|
||||
const CdmLicenseType,
|
||||
CdmAppParameterMap&,
|
||||
@@ -174,7 +175,7 @@ TEST_F(WVDrmPluginTest, ClosesSessions) {
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
TEST_F(WVDrmPluginTest, GeneratesIsoBmffKeyRequests) {
|
||||
StrictMock<MockCDM> cdm;
|
||||
StrictMock<MockCrypto> crypto;
|
||||
WVDrmPlugin plugin(&cdm, &crypto);
|
||||
@@ -226,32 +227,33 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
cdmParameters["answer"] = "42";
|
||||
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
static const char* kMimeType = "video/mp4";
|
||||
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "",
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<5>(cdmRequest),
|
||||
SetArgPointee<6>(kDefaultUrl),
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "",
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeStreaming, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<5>(cdmRequest),
|
||||
SetArgPointee<6>(kDefaultUrl),
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId,
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeRelease, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<5>(cdmRequest),
|
||||
SetArgPointee<6>(kDefaultUrl),
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
}
|
||||
|
||||
@@ -259,21 +261,118 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
String8 defaultUrl;
|
||||
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData,
|
||||
String8("video/h264"),
|
||||
String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"),
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(keySetId, initData, String8("video/h264"),
|
||||
res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Release, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GeneratesWebmKeyRequests) {
|
||||
StrictMock<MockCDM> cdm;
|
||||
StrictMock<MockCrypto> crypto;
|
||||
WVDrmPlugin plugin(&cdm, &crypto);
|
||||
|
||||
static const size_t kInitDataSize = 128;
|
||||
uint8_t initDataRaw[kInitDataSize];
|
||||
static const size_t kRequestSize = 256;
|
||||
uint8_t requestRaw[kRequestSize];
|
||||
static const uint32_t kKeySetIdSize = 32;
|
||||
uint8_t keySetIdRaw[kKeySetIdSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp);
|
||||
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
||||
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
||||
CdmKeySetId cdmKeySetId(reinterpret_cast<char *>(keySetIdRaw), kKeySetIdSize);
|
||||
Vector<uint8_t> keySetId;
|
||||
keySetId.appendArray(keySetIdRaw, kKeySetIdSize);
|
||||
|
||||
Vector<uint8_t> initData;
|
||||
initData.appendArray(initDataRaw, kInitDataSize);
|
||||
|
||||
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
|
||||
KeyedVector<String8, String8> parameters;
|
||||
CdmAppParameterMap cdmParameters;
|
||||
|
||||
parameters.add(String8("paddingScheme"), String8("BUBBLE WRAP"));
|
||||
cdmParameters["paddingScheme"] = "BUBBLE WRAP";
|
||||
parameters.add(String8("favoriteParticle"), String8("higgs boson"));
|
||||
cdmParameters["favoriteParticle"] = "higgs boson";
|
||||
parameters.add(String8("answer"), String8("6 * 9"));
|
||||
cdmParameters["answer"] = "6 * 9";
|
||||
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
static const char* kMimeType = "video/webm";
|
||||
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeStreaming, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeRelease, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
}
|
||||
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData,
|
||||
String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Release, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
|
||||
Reference in New Issue
Block a user