Corrections to support HLS
[ Merge of https://go/wvgerrit/17055 ] There are a few bugs that need to be addressed to get HLS to work. * Content ID in json init data is base64 encoded and needs to be decoded before being added to the WidevineCencHeader proto. * Protection scheme was not set in the WidevineCencHeader proto. * HLS initialization data should be sent as a CENC content identification in a license request. b/20630275 Change-Id: Ie0ac33ac061931df6f26c0afbf3e62e5d01e5041
This commit is contained in:
@@ -36,9 +36,9 @@ class InitializationData {
|
|||||||
bool ExtractHlsAttributes(const std::string& attribute_list,
|
bool ExtractHlsAttributes(const std::string& attribute_list,
|
||||||
CdmHlsMethod* method, std::vector<uint8_t>* iv,
|
CdmHlsMethod* method, std::vector<uint8_t>* iv,
|
||||||
std::string* uri);
|
std::string* uri);
|
||||||
static bool ConstructWidevineInitData(const std::string& uri,
|
static bool ConstructWidevineInitData(CdmHlsMethod method,
|
||||||
CdmInitData* output);
|
const std::string& uri,
|
||||||
|
CdmInitData* output);
|
||||||
static bool ExtractQuotedAttribute(const std::string& attribute_list,
|
static bool ExtractQuotedAttribute(const std::string& attribute_list,
|
||||||
const std::string& key,
|
const std::string& key,
|
||||||
std::string* value);
|
std::string* value);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const char kDoubleQuote = '\"';
|
|||||||
const char kLeftBracket = '[';
|
const char kLeftBracket = '[';
|
||||||
const char kRightBracket = ']';
|
const char kRightBracket = ']';
|
||||||
const std::string kBase64String = "base64,";
|
const std::string kBase64String = "base64,";
|
||||||
|
const uint32_t kFourCcCbc1 = 0x63626331;
|
||||||
const uint32_t kFourCcCbcs = 0x63626373;
|
const uint32_t kFourCcCbcs = 0x63626373;
|
||||||
|
|
||||||
// json init data key values
|
// json init data key values
|
||||||
@@ -63,7 +64,7 @@ InitializationData::InitializationData(const std::string& type,
|
|||||||
} else if (is_hls()) {
|
} else if (is_hls()) {
|
||||||
std::string uri;
|
std::string uri;
|
||||||
if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) {
|
if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) {
|
||||||
ConstructWidevineInitData(uri, &data_);
|
ConstructWidevineInitData(hls_method_, uri, &data_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,11 +333,16 @@ bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
|||||||
// ]
|
// ]
|
||||||
// }
|
// }
|
||||||
bool InitializationData::ConstructWidevineInitData(
|
bool InitializationData::ConstructWidevineInitData(
|
||||||
const std::string& uri, CdmInitData* init_data_proto) {
|
CdmHlsMethod method, const std::string& uri, CdmInitData* init_data_proto) {
|
||||||
if (!init_data_proto) {
|
if (!init_data_proto) {
|
||||||
LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter");
|
LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
|
||||||
|
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
|
||||||
|
" parameter");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t pos = uri.find(kBase64String);
|
size_t pos = uri.find(kBase64String);
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
@@ -419,8 +425,12 @@ bool InitializationData::ConstructWidevineInitData(
|
|||||||
break;
|
break;
|
||||||
case kContentIdState:
|
case kContentIdState:
|
||||||
if (tokens[i].type == JSMN_STRING) {
|
if (tokens[i].type == JSMN_STRING) {
|
||||||
content_id.assign(json_string, tokens[i].start,
|
std::string base64_content_id(json_string, tokens[i].start,
|
||||||
tokens[i].end - tokens[i].start);
|
tokens[i].end - tokens[i].start);
|
||||||
|
std::vector<uint8_t> content_id_data =
|
||||||
|
Base64Decode(base64_content_id);
|
||||||
|
content_id.assign(reinterpret_cast<const char*>(&content_id_data[0]),
|
||||||
|
content_id_data.size());
|
||||||
}
|
}
|
||||||
state = kParseState;
|
state = kParseState;
|
||||||
break;
|
break;
|
||||||
@@ -465,7 +475,10 @@ bool InitializationData::ConstructWidevineInitData(
|
|||||||
}
|
}
|
||||||
cenc_header.set_provider(provider);
|
cenc_header.set_provider(provider);
|
||||||
cenc_header.set_content_id(content_id);
|
cenc_header.set_content_id(content_id);
|
||||||
cenc_header.set_protection_scheme(htonl(kFourCcCbcs));
|
if (method == kHlsMethodAes128)
|
||||||
|
cenc_header.set_protection_scheme(htonl(kFourCcCbc1));
|
||||||
|
else
|
||||||
|
cenc_header.set_protection_scheme(htonl(kFourCcCbcs));
|
||||||
cenc_header.SerializeToString(init_data_proto);
|
cenc_header.SerializeToString(init_data_proto);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
|
|||||||
LicenseRequest_ContentIdentification* content_id =
|
LicenseRequest_ContentIdentification* content_id =
|
||||||
license_request.mutable_content_id();
|
license_request.mutable_content_id();
|
||||||
|
|
||||||
if (init_data.is_cenc()) {
|
if (init_data.is_cenc() || init_data.is_hls()) {
|
||||||
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
|
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
|
||||||
content_id->mutable_cenc_id();
|
content_id->mutable_cenc_id();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ const std::string kJsonProvider = "provider";
|
|||||||
const std::string kJsonContentId = "content_id";
|
const std::string kJsonContentId = "content_id";
|
||||||
const std::string kJsonKeyIds = "key_ids";
|
const std::string kJsonKeyIds = "key_ids";
|
||||||
|
|
||||||
|
const uint32_t kFourCcCbc1 = 0x63626331;
|
||||||
|
const uint32_t kFourCcCbcs = 0x63626373;
|
||||||
|
|
||||||
const std::string kWidevinePssh = a2bs_hex(
|
const std::string kWidevinePssh = a2bs_hex(
|
||||||
// Widevine PSSH box
|
// Widevine PSSH box
|
||||||
"00000042" // atom size
|
"00000042" // atom size
|
||||||
@@ -346,15 +350,18 @@ std::string InsertHlsAttributeInList(const std::string key,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct HlsInitDataVariant {
|
struct HlsInitDataVariant {
|
||||||
HlsInitDataVariant(const std::string& provider, const std::string& content_id,
|
HlsInitDataVariant(CdmHlsMethod method, const std::string& provider,
|
||||||
const std::string& key_id, bool success)
|
const std::string& content_id, const std::string& key_id,
|
||||||
: provider_(provider), content_id_(content_id), success_(success) {
|
bool success)
|
||||||
|
: method_(method), provider_(provider), content_id_(content_id),
|
||||||
|
success_(success) {
|
||||||
if (key_id.size() > 0) key_ids_.push_back(key_id);
|
if (key_id.size() > 0) key_ids_.push_back(key_id);
|
||||||
}
|
}
|
||||||
HlsInitDataVariant& AddKeyId(const std::string& key_id) {
|
HlsInitDataVariant& AddKeyId(const std::string& key_id) {
|
||||||
key_ids_.push_back(key_id);
|
key_ids_.push_back(key_id);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
const CdmHlsMethod method_;
|
||||||
const std::string provider_;
|
const std::string provider_;
|
||||||
const std::string content_id_;
|
const std::string content_id_;
|
||||||
std::vector<std::string> key_ids_;
|
std::vector<std::string> key_ids_;
|
||||||
@@ -588,8 +595,9 @@ TEST_P(HlsConstructionTest, InitData) {
|
|||||||
std::string uri =
|
std::string uri =
|
||||||
GenerateHlsUriData(param.provider_, param.content_id_, param.key_ids_);
|
GenerateHlsUriData(param.provider_, param.content_id_, param.key_ids_);
|
||||||
std::string value;
|
std::string value;
|
||||||
|
EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData(
|
||||||
|
param.method_, uri, &value));
|
||||||
if (param.success_) {
|
if (param.success_) {
|
||||||
EXPECT_TRUE(InitializationData::ConstructWidevineInitData(uri, &value));
|
|
||||||
WidevineCencHeader cenc_header;
|
WidevineCencHeader cenc_header;
|
||||||
EXPECT_TRUE(cenc_header.ParseFromString(value));
|
EXPECT_TRUE(cenc_header.ParseFromString(value));
|
||||||
EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR,
|
EXPECT_EQ(video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR,
|
||||||
@@ -606,28 +614,50 @@ TEST_P(HlsConstructionTest, InitData) {
|
|||||||
EXPECT_TRUE(key_id_found);
|
EXPECT_TRUE(key_id_found);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(param.provider_, cenc_header.provider());
|
EXPECT_EQ(param.provider_, cenc_header.provider());
|
||||||
EXPECT_EQ(param.content_id_, cenc_header.content_id());
|
std::vector<uint8_t> param_content_id_vec(Base64Decode(param.content_id_));
|
||||||
} else {
|
EXPECT_EQ(
|
||||||
EXPECT_FALSE(InitializationData::ConstructWidevineInitData(uri, &value));
|
std::string(param_content_id_vec.begin(), param_content_id_vec.end()),
|
||||||
|
cenc_header.content_id());
|
||||||
|
uint32_t protection_scheme = 0;
|
||||||
|
switch (param.method_) {
|
||||||
|
case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break;
|
||||||
|
case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(protection_scheme, ntohl(cenc_header.protection_scheme()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
INSTANTIATE_TEST_CASE_P(
|
INSTANTIATE_TEST_CASE_P(
|
||||||
HlsTest, HlsConstructionTest,
|
HlsTest, HlsConstructionTest,
|
||||||
::testing::Values(
|
::testing::Values(
|
||||||
HlsInitDataVariant("", kHlsTestContentId, kHlsTestKeyId1, false),
|
HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId,
|
||||||
HlsInitDataVariant(kHlsTestProvider, "", kHlsTestKeyId1, false),
|
kHlsTestKeyId1, false),
|
||||||
HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, "", false),
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId,
|
"", kHlsTestKeyId1, false),
|
||||||
kHlsTestInvalidKeyId, false),
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, kHlsTestKeyId1,
|
kHlsTestContentId, "", false),
|
||||||
true),
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId, kHlsTestKeyId1,
|
kHlsTestContentId, kHlsTestInvalidKeyId, false),
|
||||||
true)
|
HlsInitDataVariant(kHlsMethodNone, kHlsTestProvider, kHlsTestContentId,
|
||||||
|
kHlsTestKeyId1, false),
|
||||||
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
|
kHlsTestContentId, kHlsTestKeyId1, true),
|
||||||
|
HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider,
|
||||||
|
kHlsTestContentId, kHlsTestKeyId1, true),
|
||||||
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
|
kHlsTestContentId, kHlsTestKeyId1, true)
|
||||||
.AddKeyId(kHlsTestKeyId2)
|
.AddKeyId(kHlsTestKeyId2)
|
||||||
.AddKeyId(kHlsTestKeyId3),
|
.AddKeyId(kHlsTestKeyId3),
|
||||||
HlsInitDataVariant(kHlsTestProvider, kHlsTestContentId,
|
HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider,
|
||||||
kHlsTestInvalidKeyId, true)
|
kHlsTestContentId, kHlsTestKeyId1, true)
|
||||||
|
.AddKeyId(kHlsTestKeyId2)
|
||||||
|
.AddKeyId(kHlsTestKeyId3),
|
||||||
|
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
|
||||||
|
kHlsTestContentId, kHlsTestInvalidKeyId, true)
|
||||||
|
.AddKeyId(kHlsTestKeyId1),
|
||||||
|
HlsInitDataVariant(kHlsMethodSampleAes, kHlsTestProvider,
|
||||||
|
kHlsTestContentId, kHlsTestInvalidKeyId, true)
|
||||||
.AddKeyId(kHlsTestKeyId1)));
|
.AddKeyId(kHlsTestKeyId1)));
|
||||||
|
|
||||||
TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) {
|
TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) {
|
||||||
@@ -639,7 +669,7 @@ TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) {
|
|||||||
reinterpret_cast<const uint8_t*>(json.data() + json.size()));
|
reinterpret_cast<const uint8_t*>(json.data() + json.size()));
|
||||||
std::string value;
|
std::string value;
|
||||||
EXPECT_FALSE(InitializationData::ConstructWidevineInitData(
|
EXPECT_FALSE(InitializationData::ConstructWidevineInitData(
|
||||||
Base64Encode(json_init_data), &value));
|
kHlsMethodAes128, Base64Encode(json_init_data), &value));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) {
|
TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) {
|
||||||
@@ -648,7 +678,7 @@ TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) {
|
|||||||
VectorOfStrings(kHlsTestKeyId1).Generate());
|
VectorOfStrings(kHlsTestKeyId1).Generate());
|
||||||
std::string value;
|
std::string value;
|
||||||
EXPECT_FALSE(InitializationData::ConstructWidevineInitData(
|
EXPECT_FALSE(InitializationData::ConstructWidevineInitData(
|
||||||
kHlsTestUriDataFormat + json, &value));
|
kHlsMethodSampleAes, kHlsTestUriDataFormat + json, &value));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(HlsParseTest, Parse) {
|
TEST_P(HlsParseTest, Parse) {
|
||||||
@@ -700,7 +730,7 @@ INSTANTIATE_TEST_CASE_P(
|
|||||||
HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE,
|
HlsAttributeVariant(kHlsAttributeListMethodAes128, HLS_METHOD_ATTRIBUTE,
|
||||||
HLS_METHOD_AES_128, true),
|
HLS_METHOD_AES_128, true),
|
||||||
HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE,
|
HlsAttributeVariant(kHlsAttributeListMethodNone, HLS_METHOD_ATTRIBUTE,
|
||||||
HLS_METHOD_NONE, true),
|
HLS_METHOD_NONE, false),
|
||||||
HlsAttributeVariant(kHlsAttributeListKeyFormatVersionMultiple,
|
HlsAttributeVariant(kHlsAttributeListKeyFormatVersionMultiple,
|
||||||
HLS_KEYFORMAT_VERSIONS_ATTRIBUTE,
|
HLS_KEYFORMAT_VERSIONS_ATTRIBUTE,
|
||||||
HLS_KEYFORMAT_VERSION_VALUE_1, true),
|
HLS_KEYFORMAT_VERSION_VALUE_1, true),
|
||||||
|
|||||||
Reference in New Issue
Block a user