7 #include "packager/mpd/base/mpd_utils.h"
9 #include <gflags/gflags.h>
10 #include <libxml/tree.h>
12 #include "packager/base/base64.h"
13 #include "packager/base/logging.h"
14 #include "packager/base/strings/string_number_conversions.h"
15 #include "packager/base/strings/string_util.h"
16 #include "packager/media/base/language_utils.h"
17 #include "packager/media/base/protection_system_specific_info.h"
18 #include "packager/mpd/base/adaptation_set.h"
19 #include "packager/mpd/base/content_protection_element.h"
20 #include "packager/mpd/base/representation.h"
21 #include "packager/mpd/base/xml/scoped_xml_ptr.h"
24 use_legacy_vp9_codec_string,
26 "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style "
27 "vp09.xx.xx.xx... codec string will be used. Default to false as indicated "
28 "in https://github.com/google/shaka-packager/issues/406, all major "
29 "browsers and platforms already support the new 'vp09' codec string.");
34 bool IsKeyRotationDefaultKeyId(
const std::string& key_id) {
35 for (
char c : key_id) {
42 std::string TextCodecString(
const MediaInfo& media_info) {
43 CHECK(media_info.has_text_info());
44 const auto container_type = media_info.container_type();
48 if (container_type == MediaInfo::CONTAINER_TEXT) {
54 const std::string& codec = media_info.text_info().codec();
55 if (codec ==
"ttml" && container_type == MediaInfo::CONTAINER_MP4) {
64 bool HasVODOnlyFields(
const MediaInfo& media_info) {
65 return media_info.has_init_range() || media_info.has_index_range() ||
66 media_info.has_media_file_url();
69 bool HasLiveOnlyFields(
const MediaInfo& media_info) {
70 return media_info.has_init_segment_url() ||
71 media_info.has_segment_template_url();
74 void RemoveDuplicateAttributes(
75 ContentProtectionElement* content_protection_element) {
76 DCHECK(content_protection_element);
77 typedef std::map<std::string, std::string> AttributesMap;
79 AttributesMap& attributes = content_protection_element->additional_attributes;
80 if (!content_protection_element->value.empty())
81 attributes.erase(
"value");
83 if (!content_protection_element->scheme_id_uri.empty())
84 attributes.erase(
"schemeIdUri");
87 std::string GetLanguage(
const MediaInfo& media_info) {
89 if (media_info.has_audio_info()) {
90 lang = media_info.audio_info().language();
91 }
else if (media_info.has_text_info()) {
92 lang = media_info.text_info().language();
97 std::string GetCodecs(
const MediaInfo& media_info) {
98 CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(),
99 media_info.has_text_info()));
101 if (media_info.has_video_info()) {
102 if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) {
103 std::string codec = media_info.video_info().codec().substr(0, 4);
110 if (FLAGS_use_legacy_vp9_codec_string) {
115 return media_info.video_info().codec();
118 if (media_info.has_audio_info())
119 return media_info.audio_info().codec();
121 if (media_info.has_text_info())
122 return TextCodecString(media_info);
128 std::string GetBaseCodec(
const MediaInfo& media_info) {
130 if (media_info.has_video_info()) {
131 codec = media_info.video_info().codec();
132 }
else if (media_info.has_audio_info()) {
133 codec = media_info.audio_info().codec();
134 }
else if (media_info.has_text_info()) {
135 codec = media_info.text_info().codec();
139 size_t dot = codec.find(
'.');
140 if (dot != std::string::npos) {
146 std::string GetAdaptationSetKey(
const MediaInfo& media_info,
150 if (media_info.has_video_info()) {
151 key.append(
"video:");
152 }
else if (media_info.has_audio_info()) {
153 key.append(
"audio:");
154 }
else if (media_info.has_text_info()) {
155 key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type()));
158 key.append(
"unknown:");
161 key.append(MediaInfo_ContainerType_Name(media_info.container_type()));
164 key.append(GetBaseCodec(media_info));
167 key.append(GetLanguage(media_info));
171 if (media_info.video_info().has_playback_rate()) {
172 key.append(
":trick_play");
175 if (!media_info.dash_accessibilities().empty()) {
176 key.append(
":accessibility_");
177 for (
const std::string& accessibility : media_info.dash_accessibilities())
178 key.append(accessibility);
181 if (!media_info.dash_roles().empty()) {
182 key.append(
":roles_");
183 for (
const std::string& role : media_info.dash_roles())
190 std::string SecondsToXmlDuration(
double seconds) {
196 return "PT" + base::DoubleToString(seconds) +
"S";
199 bool GetDurationAttribute(xmlNodePtr node,
float* duration) {
202 static const char kDuration[] =
"duration";
203 xml::scoped_xml_ptr<xmlChar> duration_value(
204 xmlGetProp(node, BAD_CAST kDuration));
209 double duration_double_precision = 0.0;
210 if (!base::StringToDouble(
reinterpret_cast<const char*
>(duration_value.get()),
211 &duration_double_precision)) {
215 *duration =
static_cast<float>(duration_double_precision);
219 bool MoreThanOneTrue(
bool b1,
bool b2,
bool b3) {
220 return (b1 && b2) || (b2 && b3) || (b3 && b1);
223 bool AtLeastOneTrue(
bool b1,
bool b2,
bool b3) {
224 return b1 || b2 || b3;
227 bool OnlyOneTrue(
bool b1,
bool b2,
bool b3) {
228 return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3);
232 bool HexToUUID(
const std::string& data, std::string* uuid_format) {
234 const size_t kExpectedUUIDSize = 16;
235 if (data.size() != kExpectedUUIDSize) {
236 LOG(ERROR) <<
"UUID size is expected to be " << kExpectedUUIDSize
237 <<
" but is " << data.size() <<
" and the data in hex is "
238 << base::HexEncode(data.data(), data.size());
242 const std::string hex_encoded =
243 base::ToLowerASCII(base::HexEncode(data.data(), data.size()));
244 DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2);
245 base::StringPiece all(hex_encoded);
249 base::StringPiece first = all.substr(0, 8);
250 base::StringPiece second = all.substr(8, 4);
251 base::StringPiece third = all.substr(12, 4);
252 base::StringPiece fourth = all.substr(16, 4);
253 base::StringPiece fifth = all.substr(20, 12);
256 const size_t kHumanReadableUUIDSize = 36;
257 uuid_format->reserve(kHumanReadableUUIDSize);
258 first.CopyToString(uuid_format);
259 uuid_format->append(
"-");
260 second.AppendToString(uuid_format);
261 uuid_format->append(
"-");
262 third.AppendToString(uuid_format);
263 uuid_format->append(
"-");
264 fourth.AppendToString(uuid_format);
265 uuid_format->append(
"-");
266 fifth.AppendToString(uuid_format);
270 void UpdateContentProtectionPsshHelper(
271 const std::string& drm_uuid,
272 const std::string& pssh,
273 std::list<ContentProtectionElement>* content_protection_elements) {
274 const std::string drm_uuid_schemd_id_uri_form =
"urn:uuid:" + drm_uuid;
275 for (std::list<ContentProtectionElement>::iterator protection =
276 content_protection_elements->begin();
277 protection != content_protection_elements->end(); ++protection) {
278 if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) {
282 for (std::vector<Element>::iterator subelement =
283 protection->subelements.begin();
284 subelement != protection->subelements.end(); ++subelement) {
285 if (subelement->name == kPsshElementName) {
288 protection->subelements.erase(subelement);
309 ContentProtectionElement content_protection;
310 content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form;
316 content_protection_elements->push_back(content_protection);
324 const char kMarlinUUID[] =
"5e629af5-38da-4063-8977-97ffbd9902d4";
327 const char kFairPlayUUID[] =
"29701fe4-3cc7-4a34-8c5b-ae90c7439a47";
329 const char kPlayReadyUUID[] =
"9a04f079-9840-4286-ab92-e65be0885f95";
332 const char kContentProtectionValueMSPR20[] =
"MSPR 2.0";
334 Element GenerateMarlinContentIds(
const std::string& key_id) {
336 static const char kMarlinContentIdName[] =
"mas:MarlinContentId";
337 static const char kMarlinContentIdPrefix[] =
"urn:marlin:kid:";
338 static const char kMarlinContentIdsName[] =
"mas:MarlinContentIds";
340 Element marlin_content_id;
341 marlin_content_id.name = kMarlinContentIdName;
342 marlin_content_id.content =
343 kMarlinContentIdPrefix +
344 base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size()));
346 Element marlin_content_ids;
347 marlin_content_ids.name = kMarlinContentIdsName;
348 marlin_content_ids.subelements.push_back(marlin_content_id);
350 return marlin_content_ids;
353 Element GenerateCencPsshElement(
const std::string& pssh) {
354 std::string base64_encoded_pssh;
355 base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()),
356 &base64_encoded_pssh);
358 cenc_pssh.name = kPsshElementName;
359 cenc_pssh.content = base64_encoded_pssh;
365 Element GenerateMsprProElement(
const std::string& pssh) {
366 std::unique_ptr<media::PsshBoxBuilder> b =
368 reinterpret_cast<const uint8_t*
>(pssh.data()),
372 const std::vector<uint8_t> *p_pssh = &b->pssh_data();
373 std::string base64_encoded_mspr;
376 reinterpret_cast<const char*
>(p_pssh->data()),
381 mspr_pro.name = kMsproElementName;
382 mspr_pro.content = base64_encoded_mspr;
388 template <
typename ContentProtectionParent>
389 void AddContentProtectionElementsHelperTemplated(
390 const MediaInfo& media_info,
391 ContentProtectionParent* parent) {
393 if (!media_info.has_protected_content())
396 const MediaInfo::ProtectedContent& protected_content =
397 media_info.protected_content();
401 const bool is_mp4_container =
402 media_info.container_type() == MediaInfo::CONTAINER_MP4;
403 std::string key_id_uuid_format;
404 if (protected_content.has_default_key_id() &&
405 !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) {
406 if (!
HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) {
407 LOG(ERROR) <<
"Failed to convert default key ID into UUID format.";
411 if (is_mp4_container) {
412 ContentProtectionElement mp4_content_protection;
413 mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme;
414 mp4_content_protection.value = protected_content.protection_scheme();
415 if (!key_id_uuid_format.empty()) {
416 mp4_content_protection.additional_attributes[
"cenc:default_KID"] =
420 parent->AddContentProtectionElement(mp4_content_protection);
423 for (
const auto& entry : protected_content.content_protection_entry()) {
424 if (!entry.has_uuid()) {
426 <<
"ContentProtectionEntry was specified but no UUID is set for "
427 << entry.name_version() <<
", skipping.";
431 ContentProtectionElement drm_content_protection;
433 if (entry.has_name_version())
434 drm_content_protection.value = entry.name_version();
436 if (entry.uuid() == kFairPlayUUID) {
437 VLOG(1) <<
"Skipping FairPlay ContentProtection element as FairPlay does "
438 "not support DASH signaling.";
440 }
else if (entry.uuid() == kMarlinUUID) {
442 drm_content_protection.scheme_id_uri =
443 "urn:uuid:" + base::ToUpperASCII(entry.uuid());
444 drm_content_protection.subelements.push_back(
445 GenerateMarlinContentIds(protected_content.default_key_id()));
447 drm_content_protection.scheme_id_uri =
"urn:uuid:" + entry.uuid();
448 if (!entry.pssh().empty()) {
449 drm_content_protection.subelements.push_back(
450 GenerateCencPsshElement(entry.pssh()));
451 if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) {
452 drm_content_protection.subelements.push_back(
453 GenerateMsprProElement(entry.pssh()));
454 drm_content_protection.value = kContentProtectionValueMSPR20;
459 if (!key_id_uuid_format.empty() && !is_mp4_container) {
460 drm_content_protection.additional_attributes[
"cenc:default_KID"] =
464 parent->AddContentProtectionElement(drm_content_protection);
467 VLOG_IF(1, protected_content.content_protection_entry().size() == 0)
468 <<
"The media is encrypted but no content protection specified (can "
469 "happen with key rotation).";
475 AddContentProtectionElementsHelperTemplated(media_info, parent);
480 AddContentProtectionElementsHelperTemplated(media_info, parent);
All the methods that are virtual are virtual for mocking.
bool HexToUUID(const std::string &data, std::string *uuid_format)
std::string LanguageToShortestForm(const std::string &language)
void AddContentProtectionElements(const MediaInfo &media_info, Representation *parent)