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:
Rahul Frias
2016-01-07 13:06:42 -08:00
parent 53463f292d
commit 355471c408
5 changed files with 571 additions and 34 deletions

View File

@@ -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