Modify initialization data to support HLS
[ Merge of http://go/wvgerrit/16290 ] HLS uses an EXT-X-KEY tag and attribute list in the media playlist to identify the key and method used to encrypt media segments. This allows for the attributes to be parsed and extracted. b/20630275 Change-Id: I2c4a419022f933b7b34b64dc48930f167abe65c6
This commit is contained in:
@@ -7,26 +7,35 @@
|
||||
#include "buffer_reader.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
InitializationData::InitializationData(const std::string& type,
|
||||
const CdmInitData& data)
|
||||
: type_(type), is_cenc_(false), is_webm_(false) {
|
||||
: type_(type), is_cenc_(false), is_hls_(false), is_webm_(false) {
|
||||
if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE ||
|
||||
type == CENC_INIT_DATA_FORMAT) {
|
||||
is_cenc_ = true;
|
||||
} else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE ||
|
||||
type == WEBM_INIT_DATA_FORMAT) {
|
||||
is_webm_ = true;
|
||||
} else if (type == HLS_INIT_DATA_FORMAT) {
|
||||
is_hls_ = true;
|
||||
}
|
||||
|
||||
if (is_supported()) {
|
||||
if (is_cenc()) {
|
||||
ExtractWidevinePssh(data, &data_);
|
||||
} else {
|
||||
} else if (is_webm()) {
|
||||
data_ = data;
|
||||
} else if (is_hls()) {
|
||||
CdmInitData init_data;
|
||||
if (ExtractHlsAttributes(data, &hls_data_.method, &hls_data_.iv,
|
||||
&hls_data_.uri, &init_data)) {
|
||||
ExtractWidevinePssh(init_data, &data_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,19 +70,22 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
// atom size, used for skipping.
|
||||
uint64_t size;
|
||||
if (!reader.Read4Into8(&size)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom size.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read atom size.");
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> atom_type;
|
||||
if (!reader.ReadVec(&atom_type, 4)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom type.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read atom type.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size == 1) {
|
||||
if (!reader.Read8(&size)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom "
|
||||
"size.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read 64-bit "
|
||||
"atom size.");
|
||||
return false;
|
||||
}
|
||||
} else if (size == 0) {
|
||||
@@ -82,10 +94,12 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
|
||||
// "pssh"
|
||||
if (memcmp(&atom_type[0], "pssh", 4)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: PSSH literal not present.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: PSSH literal not present.");
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
|
||||
"the atom.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -94,15 +108,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
// version
|
||||
uint8_t version;
|
||||
if (!reader.Read1(&version)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH "
|
||||
"version.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version > 1) {
|
||||
// unrecognized version - skip.
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
|
||||
"the atom.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -110,25 +127,31 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
|
||||
// flags
|
||||
if (!reader.SkipBytes(3)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the PSSH "
|
||||
"flags.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// system id
|
||||
std::vector<uint8_t> system_id;
|
||||
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read system ID.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read system ID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) {
|
||||
// skip non-Widevine PSSH boxes.
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
|
||||
"the atom.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Skipping non-Widevine "
|
||||
"PSSH.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -136,11 +159,14 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
// v1 has additional fields for key IDs. We can skip them.
|
||||
uint32_t num_key_ids;
|
||||
if (!reader.Read4(&num_key_ids)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read num key "
|
||||
"IDs.");
|
||||
return false;
|
||||
}
|
||||
if (!reader.SkipBytes(num_key_ids * 16)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip key IDs.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -148,13 +174,16 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
// size of PSSH data
|
||||
uint32_t data_length;
|
||||
if (!reader.Read4(&data_length)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data "
|
||||
"size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
output->clear();
|
||||
if (!reader.ReadString(output, data_length)) {
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data.");
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -165,4 +194,175 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it
|
||||
// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out
|
||||
// method, IV, URI and WV init data.
|
||||
//
|
||||
// An example of a widevine supported attribute list from an HLS media playlist
|
||||
// is,
|
||||
// "EXT-X-KEY: METHOD=SAMPLE-AES, \"
|
||||
// "URI=”http://www.youtube.com/license-service”, \"
|
||||
// "IV=0x6df49213a781e338628d0e9c812d328e, \"
|
||||
// "KEYFORMAT=”com.widevine.alpha”, \"
|
||||
// "KEYFORMATVERSION=”1”, \"
|
||||
// "X-WV-INITDATA=0x9e1a3af3de74ae3606e931fee285e3402e172732aba9a16a0e1441f1e"
|
||||
bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
||||
CdmHlsMethod* method,
|
||||
std::vector<uint8_t>* iv,
|
||||
std::string* uri,
|
||||
CdmInitData* init_data) {
|
||||
std::string value;
|
||||
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE,
|
||||
&value)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
||||
"keyformat value");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS "
|
||||
"keyformat value: %s",
|
||||
value.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSION_ATTRIBUTE,
|
||||
&value)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
||||
"keyformat version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.compare(HLS_KEYFORMAT_VERSION_VALUE_1)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat "
|
||||
"version is not supported: %s",
|
||||
value.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
||||
"method");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.compare(HLS_METHOD_AES_128) == 0) {
|
||||
*method = kHlsMethodAes128;
|
||||
} else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) {
|
||||
*method = kHlsMethodSampleAes;
|
||||
} else if (value.compare(HLS_METHOD_NONE) == 0) {
|
||||
*method = kHlsMethodNone;
|
||||
} else {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS method "
|
||||
"unrecognized: %s",
|
||||
value.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute "
|
||||
"not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute "
|
||||
"not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> init_data_hex;
|
||||
if (!ExtractHexAttribute(attribute_list, HLS_INITDATA_ATTRIBUTE,
|
||||
&init_data_hex)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS initdata "
|
||||
"attribute not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
init_data->assign(reinterpret_cast<char*>(init_data_hex.data()),
|
||||
init_data_hex.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InitializationData::ExtractQuotedAttribute(
|
||||
const std::string& attribute_list, const std::string& key,
|
||||
std::string* value) {
|
||||
bool result = ExtractAttribute(attribute_list, key, value);
|
||||
if (value->size() < 2 || value->at(0) != '\"' ||
|
||||
value->at(value->size() - 1) != '\"')
|
||||
return false;
|
||||
*value = value->substr(1, value->size() - 2);
|
||||
if (value->find('\"') != std::string::npos) return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InitializationData::ExtractHexAttribute(const std::string& attribute_list,
|
||||
const std::string& key,
|
||||
std::vector<uint8_t>* value) {
|
||||
std::string val;
|
||||
bool result = ExtractAttribute(attribute_list, key, &val);
|
||||
if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' ||
|
||||
((val[1] != 'x') && (val[1] != 'X')))
|
||||
return false;
|
||||
for (size_t i = 2; i < val.size(); ++i) {
|
||||
if (!isxdigit(val[i])) return false;
|
||||
}
|
||||
*value = a2b_hex(val.substr(2, val.size() - 2));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InitializationData::ExtractAttribute(const std::string& attribute_list,
|
||||
const std::string& key,
|
||||
std::string* value) {
|
||||
// validate the key
|
||||
for (size_t i = 0; i < key.size(); ++i)
|
||||
if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false;
|
||||
|
||||
bool found = false;
|
||||
size_t pos = 0;
|
||||
// Find the key followed by '='
|
||||
while (!found) {
|
||||
pos = attribute_list.find(key, pos);
|
||||
if (pos == std::string::npos) return false;
|
||||
pos += key.size();
|
||||
if (attribute_list[pos] != '=') continue;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (attribute_list.size() <= ++pos) return false;
|
||||
|
||||
size_t end_pos = pos;
|
||||
found = false;
|
||||
// Find the next comma outside the quote or end of string
|
||||
while (!found) {
|
||||
end_pos = attribute_list.find(',', end_pos);
|
||||
if (end_pos != std::string::npos && attribute_list[pos] == '\"' &&
|
||||
attribute_list[end_pos - 1] != '\"')
|
||||
continue;
|
||||
|
||||
if (end_pos == std::string::npos)
|
||||
end_pos = attribute_list.size() - 1;
|
||||
else
|
||||
--end_pos;
|
||||
found = true;
|
||||
}
|
||||
|
||||
*value = attribute_list.substr(pos, end_pos - pos + 1);
|
||||
|
||||
// validate the value
|
||||
for (size_t i = 0; i < value->size(); ++i)
|
||||
if (!isgraph(value->at(i))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
Reference in New Issue
Block a user