From 066ad92569712eb3825d84cb685dd464197833aa Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 26 May 2023 18:40:59 -0700 Subject: [PATCH 01/11] feat(DASH):Add video transfer characteristics. --- .../output.mpd | 1 + .../output.mpd | 2 ++ .../test/testdata/hdr10-with-encryption/output.mpd | 1 + packager/mpd/base/adaptation_set.cc | 8 ++++++++ packager/mpd/base/adaptation_set.h | 14 ++++++++++++++ packager/mpd/base/mpd_utils.cc | 9 +++++++++ packager/mpd/base/period.cc | 14 ++++++++++++++ 7 files changed, 49 insertions(+) diff --git a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd index b81c94aa08b..9f02235c0b7 100644 --- a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd @@ -3,6 +3,7 @@ + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd index 0950e11dcd9..04c010c0081 100644 --- a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd @@ -3,6 +3,7 @@ + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== @@ -15,6 +16,7 @@ + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/app/test/testdata/hdr10-with-encryption/output.mpd b/packager/app/test/testdata/hdr10-with-encryption/output.mpd index 9fd7a8654ae..56001f85c7a 100644 --- a/packager/app/test/testdata/hdr10-with-encryption/output.mpd +++ b/packager/app/test/testdata/hdr10-with-encryption/output.mpd @@ -3,6 +3,7 @@ + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== diff --git a/packager/mpd/base/adaptation_set.cc b/packager/mpd/base/adaptation_set.cc index 1f3cbe1d822..5720cf5edfa 100644 --- a/packager/mpd/base/adaptation_set.cc +++ b/packager/mpd/base/adaptation_set.cc @@ -290,6 +290,14 @@ base::Optional AdaptationSet::GetXml() { } } + // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1 + if (IsVideo() && transfer_characteristics_ > 0 && + !adaptation_set.AddSupplementalProperty( + "urn:mpeg:mpegB:cicp:TransferCharacteristics", + std::to_string(transfer_characteristics_))) { + return base::nullopt; + } + // Note: must be checked before checking segments_aligned_ (below). So that // segments_aligned_ is set before checking below. if (mpd_options_.mpd_type == MpdType::kStatic) { diff --git a/packager/mpd/base/adaptation_set.h b/packager/mpd/base/adaptation_set.h index dd9fa3942d7..d6ebc624a61 100644 --- a/packager/mpd/base/adaptation_set.h +++ b/packager/mpd/base/adaptation_set.h @@ -180,6 +180,17 @@ class AdaptationSet { /// @param codec is the new codec to be set. void set_codec(const std::string& codec) { codec_ = codec; }; + /// @return transfer_characteristics. + const uint32_t transfer_characteristics() const { + return transfer_characteristics_; + } + + /// Set AdaptationSet's video transfer characteristics. + /// @param transfer_characteristics is the video transfer characteristics. + void set_transfer_characteristics(const uint32_t& transfer_characteristics) { + transfer_characteristics_ = transfer_characteristics; + }; + protected: /// @param language is the language of this AdaptationSet. Mainly relevant for /// audio. @@ -314,6 +325,9 @@ class AdaptationSet { // and HD videos in different AdaptationSets can share the same trick play // stream. std::vector trick_play_references_; + + // Transfer characteristics. + uint32_t transfer_characteristics_ = 0; }; } // namespace shaka diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 018551a80fd..e4b833fc661 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -162,6 +162,15 @@ std::string GetAdaptationSetKey(const MediaInfo& media_info, if (!ignore_codec) { key.append(":"); key.append(GetBaseCodec(media_info)); + + if (GetBaseCodec(media_info).find("dvh") == 0) { + key.append(":"); + key.append(std::to_string(16)); + } else if (media_info.video_info().has_transfer_characteristics()) { + key.append(":"); + key.append( + std::to_string(media_info.video_info().transfer_characteristics())); + } } key.append(":"); key.append(GetLanguage(media_info)); diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index e1285fdf0b1..e7376c4d786 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -269,6 +269,20 @@ bool Period::SetNewAdaptationSetAttributes( } } + // Set transfer characteristics. + // https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf - 4.2.5.1 + // ISO/IEC 23001-8 MPEG systems technologies — Part 8: Coding-independent + // code points. https://en.wikipedia.org/wiki/Coding-independent_code_points + // - Common CCIP values. + // Dolby vision: + // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH + if (new_adaptation_set->codec().find("dvh") == 0) { + new_adaptation_set->set_transfer_characteristics(16); + } else if (media_info.video_info().has_transfer_characteristics()) { + new_adaptation_set->set_transfer_characteristics( + media_info.video_info().transfer_characteristics()); + } + } else if (media_info.has_text_info()) { // IOP requires all AdaptationSets to have (sub)segmentAlignment set to // true, so carelessly set it to true. From e0366c1a6cae77918f61ba13005290a5ebdd91c5 Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 21 Jul 2023 21:22:20 -0700 Subject: [PATCH 02/11] Improving readability. --- packager/mpd/base/mpd_utils.cc | 2 +- packager/mpd/base/mpd_utils.h | 1 + packager/mpd/base/period.cc | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index e4b833fc661..4840f2020d8 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -165,7 +165,7 @@ std::string GetAdaptationSetKey(const MediaInfo& media_info, if (GetBaseCodec(media_info).find("dvh") == 0) { key.append(":"); - key.append(std::to_string(16)); + key.append(std::to_string(kTransferFunctionPQ)); } else if (media_info.video_info().has_transfer_characteristics()) { key.append(":"); key.append( diff --git a/packager/mpd/base/mpd_utils.h b/packager/mpd/base/mpd_utils.h index 3bad952fa22..782f6bb12ad 100644 --- a/packager/mpd/base/mpd_utils.h +++ b/packager/mpd/base/mpd_utils.h @@ -25,6 +25,7 @@ struct SegmentInfo; const char kEncryptedMp4Scheme[] = "urn:mpeg:dash:mp4protection:2011"; const char kPsshElementName[] = "cenc:pssh"; const char kMsproElementName[] = "mspr:pro"; +const uint32_t kTransferFunctionPQ = 16; bool HasVODOnlyFields(const MediaInfo& media_info); diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index e7376c4d786..a2f2f99d9fe 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -277,7 +277,7 @@ bool Period::SetNewAdaptationSetAttributes( // Dolby vision: // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH if (new_adaptation_set->codec().find("dvh") == 0) { - new_adaptation_set->set_transfer_characteristics(16); + new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); } else if (media_info.video_info().has_transfer_characteristics()) { new_adaptation_set->set_transfer_characteristics( media_info.video_info().transfer_characteristics()); From 88696b9d2b666ad09af54ad706a87fb7b162680a Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 24 Jul 2023 15:08:29 -0700 Subject: [PATCH 03/11] Add comment and flip order for Dolby special case --- packager/mpd/base/mpd_utils.cc | 983 +++++++++++++++++---------------- 1 file changed, 492 insertions(+), 491 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 4840f2020d8..df5ec4c24a8 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -1,492 +1,493 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include "packager/mpd/base/mpd_utils.h" - -#include -#include - -#include "packager/base/base64.h" -#include "packager/base/logging.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/base/strings/string_util.h" -#include "packager/media/base/language_utils.h" -#include "packager/media/base/protection_system_specific_info.h" -#include "packager/mpd/base/adaptation_set.h" -#include "packager/mpd/base/content_protection_element.h" -#include "packager/mpd/base/representation.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - -DEFINE_bool( - use_legacy_vp9_codec_string, - false, - "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " - "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " - "in https://github.com/shaka-project/shaka-packager/issues/406, all major " - "browsers and platforms already support the new 'vp09' codec string."); - -namespace shaka { -namespace { - -bool IsKeyRotationDefaultKeyId(const std::string& key_id) { - for (char c : key_id) { - if (c != '\0') - return false; - } - return true; -} - -std::string TextCodecString(const MediaInfo& media_info) { - CHECK(media_info.has_text_info()); - const auto container_type = media_info.container_type(); - - // Codecs are not needed when mimeType is "text/*". Having a codec would be - // redundant. - if (container_type == MediaInfo::CONTAINER_TEXT) { - return ""; - } - - // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override - // the default codec value. - const std::string& codec = media_info.text_info().codec(); - if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { - return "stpp"; - } - - return codec; -} - -} // namespace - -bool HasVODOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_range() || media_info.has_index_range() || - media_info.has_media_file_url(); -} - -bool HasLiveOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_segment_url() || - media_info.has_segment_template_url(); -} - -void RemoveDuplicateAttributes( - ContentProtectionElement* content_protection_element) { - DCHECK(content_protection_element); - typedef std::map AttributesMap; - - AttributesMap& attributes = content_protection_element->additional_attributes; - if (!content_protection_element->value.empty()) - attributes.erase("value"); - - if (!content_protection_element->scheme_id_uri.empty()) - attributes.erase("schemeIdUri"); -} - -std::string GetLanguage(const MediaInfo& media_info) { - std::string lang; - if (media_info.has_audio_info()) { - lang = media_info.audio_info().language(); - } else if (media_info.has_text_info()) { - lang = media_info.text_info().language(); - } - return LanguageToShortestForm(lang); -} - -std::string GetCodecs(const MediaInfo& media_info) { - CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), - media_info.has_text_info())); - - if (media_info.has_video_info()) { - if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { - std::string codec = media_info.video_info().codec().substr(0, 4); - // media_info.video_info().codec() contains new revised codec string - // specified by "VPx in ISO BMFF" document, which is not compatible to - // old codec strings in WebM. Hack it here before all browsers support - // new codec strings. - if (codec == "vp08") - return "vp8"; - if (FLAGS_use_legacy_vp9_codec_string) { - if (codec == "vp09") - return "vp9"; - } - } - return media_info.video_info().codec(); - } - - if (media_info.has_audio_info()) - return media_info.audio_info().codec(); - - if (media_info.has_text_info()) - return TextCodecString(media_info); - - NOTREACHED(); - return ""; -} - -std::string GetBaseCodec(const MediaInfo& media_info) { - std::string codec; - if (media_info.has_video_info()) { - codec = media_info.video_info().codec(); - } else if (media_info.has_audio_info()) { - codec = media_info.audio_info().codec(); - } else if (media_info.has_text_info()) { - codec = media_info.text_info().codec(); - } - // Convert, for example, "mp4a.40.2" to simply "mp4a". - // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. - size_t dot = codec.find('.'); - if (dot != std::string::npos) { - codec.erase(dot); - } - return codec; -} - -std::string GetAdaptationSetKey(const MediaInfo& media_info, - bool ignore_codec) { - std::string key; - - if (media_info.has_video_info()) { - key.append("video:"); - } else if (media_info.has_audio_info()) { - key.append("audio:"); - } else if (media_info.has_text_info()) { - key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); - key.append(":"); - } else { - key.append("unknown:"); - } - - key.append(MediaInfo_ContainerType_Name(media_info.container_type())); - if (!ignore_codec) { - key.append(":"); - key.append(GetBaseCodec(media_info)); - - if (GetBaseCodec(media_info).find("dvh") == 0) { - key.append(":"); - key.append(std::to_string(kTransferFunctionPQ)); - } else if (media_info.video_info().has_transfer_characteristics()) { - key.append(":"); - key.append( - std::to_string(media_info.video_info().transfer_characteristics())); - } - } - key.append(":"); - key.append(GetLanguage(media_info)); - - // Trick play streams of the same original stream, but possibly with - // different trick_play_factors, belong to the same trick play AdaptationSet. - if (media_info.video_info().has_playback_rate()) { - key.append(":trick_play"); - } - - if (!media_info.dash_accessibilities().empty()) { - key.append(":accessibility_"); - for (const std::string& accessibility : media_info.dash_accessibilities()) - key.append(accessibility); - } - - if (!media_info.dash_roles().empty()) { - key.append(":roles_"); - for (const std::string& role : media_info.dash_roles()) - key.append(role); - } - - return key; -} - -std::string SecondsToXmlDuration(double seconds) { - // Chrome internally uses time accurate to microseconds, which is implemented - // per MSE spec (https://www.w3.org/TR/media-source/). - // We need a string formatter that has at least microseconds accuracy for a - // normal video (with duration up to 3 hours). Chrome's DoubleToString - // implementation meets the requirement. - return "PT" + base::DoubleToString(seconds) + "S"; -} - -bool GetDurationAttribute(xmlNodePtr node, float* duration) { - DCHECK(node); - DCHECK(duration); - static const char kDuration[] = "duration"; - xml::scoped_xml_ptr duration_value( - xmlGetProp(node, BAD_CAST kDuration)); - - if (!duration_value) - return false; - - double duration_double_precision = 0.0; - if (!base::StringToDouble(reinterpret_cast(duration_value.get()), - &duration_double_precision)) { - return false; - } - - *duration = static_cast(duration_double_precision); - return true; -} - -bool MoreThanOneTrue(bool b1, bool b2, bool b3) { - return (b1 && b2) || (b2 && b3) || (b3 && b1); -} - -bool AtLeastOneTrue(bool b1, bool b2, bool b3) { - return b1 || b2 || b3; -} - -bool OnlyOneTrue(bool b1, bool b2, bool b3) { - return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); -} - -// Coverts binary data into human readable UUID format. -bool HexToUUID(const std::string& data, std::string* uuid_format) { - DCHECK(uuid_format); - const size_t kExpectedUUIDSize = 16; - if (data.size() != kExpectedUUIDSize) { - LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize - << " but is " << data.size() << " and the data in hex is " - << base::HexEncode(data.data(), data.size()); - return false; - } - - const std::string hex_encoded = - base::ToLowerASCII(base::HexEncode(data.data(), data.size())); - DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); - base::StringPiece all(hex_encoded); - // Note UUID has 5 parts separated with dashes. - // e.g. 123e4567-e89b-12d3-a456-426655440000 - // These StringPieces have each part. - base::StringPiece first = all.substr(0, 8); - base::StringPiece second = all.substr(8, 4); - base::StringPiece third = all.substr(12, 4); - base::StringPiece fourth = all.substr(16, 4); - base::StringPiece fifth = all.substr(20, 12); - - // 32 hexadecimal characters with 4 hyphens. - const size_t kHumanReadableUUIDSize = 36; - uuid_format->reserve(kHumanReadableUUIDSize); - first.CopyToString(uuid_format); - uuid_format->append("-"); - second.AppendToString(uuid_format); - uuid_format->append("-"); - third.AppendToString(uuid_format); - uuid_format->append("-"); - fourth.AppendToString(uuid_format); - uuid_format->append("-"); - fifth.AppendToString(uuid_format); - return true; -} - -void UpdateContentProtectionPsshHelper( - const std::string& drm_uuid, - const std::string& pssh, - std::list* content_protection_elements) { - const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; - for (std::list::iterator protection = - content_protection_elements->begin(); - protection != content_protection_elements->end(); ++protection) { - if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { - continue; - } - - for (std::vector::iterator subelement = - protection->subelements.begin(); - subelement != protection->subelements.end(); ++subelement) { - if (subelement->name == kPsshElementName) { - // For now, we want to remove the PSSH element because some players do - // not support updating pssh. - protection->subelements.erase(subelement); - - // TODO(rkuroiwa): Uncomment this and remove the line above when - // shaka-player supports updating PSSH. - // subelement->content = pssh; - return; - } - } - - // Reaching here means does not exist under the - // ContentProtection element. Add it. - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // protection->subelements.push_back(cenc_pssh); - return; - } - - // Reaching here means that ContentProtection for the DRM does not exist. - // Add it. - ContentProtectionElement content_protection; - content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // content_protection.subelements.push_back(cenc_pssh); - content_protection_elements->push_back(content_protection); - return; -} - -namespace { - -// UUID for Marlin Adaptive Streaming Specification – Simple Profile from -// https://dashif.org/identifiers/content_protection/. -const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; -// Unofficial FairPlay system id extracted from -// https://forums.developer.apple.com/thread/6185. -const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; -// String representation of media::kPlayReadySystemId. -const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/mpd/base/mpd_utils.h" + +#include +#include + +#include "packager/base/base64.h" +#include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/string_util.h" +#include "packager/media/base/language_utils.h" +#include "packager/media/base/protection_system_specific_info.h" +#include "packager/mpd/base/adaptation_set.h" +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/representation.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + +DEFINE_bool( + use_legacy_vp9_codec_string, + false, + "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " + "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " + "in https://github.com/shaka-project/shaka-packager/issues/406, all major " + "browsers and platforms already support the new 'vp09' codec string."); + +namespace shaka { +namespace { + +bool IsKeyRotationDefaultKeyId(const std::string& key_id) { + for (char c : key_id) { + if (c != '\0') + return false; + } + return true; +} + +std::string TextCodecString(const MediaInfo& media_info) { + CHECK(media_info.has_text_info()); + const auto container_type = media_info.container_type(); + + // Codecs are not needed when mimeType is "text/*". Having a codec would be + // redundant. + if (container_type == MediaInfo::CONTAINER_TEXT) { + return ""; + } + + // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override + // the default codec value. + const std::string& codec = media_info.text_info().codec(); + if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { + return "stpp"; + } + + return codec; +} + +} // namespace + +bool HasVODOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_range() || media_info.has_index_range() || + media_info.has_media_file_url(); +} + +bool HasLiveOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_segment_url() || + media_info.has_segment_template_url(); +} + +void RemoveDuplicateAttributes( + ContentProtectionElement* content_protection_element) { + DCHECK(content_protection_element); + typedef std::map AttributesMap; + + AttributesMap& attributes = content_protection_element->additional_attributes; + if (!content_protection_element->value.empty()) + attributes.erase("value"); + + if (!content_protection_element->scheme_id_uri.empty()) + attributes.erase("schemeIdUri"); +} + +std::string GetLanguage(const MediaInfo& media_info) { + std::string lang; + if (media_info.has_audio_info()) { + lang = media_info.audio_info().language(); + } else if (media_info.has_text_info()) { + lang = media_info.text_info().language(); + } + return LanguageToShortestForm(lang); +} + +std::string GetCodecs(const MediaInfo& media_info) { + CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), + media_info.has_text_info())); + + if (media_info.has_video_info()) { + if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { + std::string codec = media_info.video_info().codec().substr(0, 4); + // media_info.video_info().codec() contains new revised codec string + // specified by "VPx in ISO BMFF" document, which is not compatible to + // old codec strings in WebM. Hack it here before all browsers support + // new codec strings. + if (codec == "vp08") + return "vp8"; + if (FLAGS_use_legacy_vp9_codec_string) { + if (codec == "vp09") + return "vp9"; + } + } + return media_info.video_info().codec(); + } + + if (media_info.has_audio_info()) + return media_info.audio_info().codec(); + + if (media_info.has_text_info()) + return TextCodecString(media_info); + + NOTREACHED(); + return ""; +} + +std::string GetBaseCodec(const MediaInfo& media_info) { + std::string codec; + if (media_info.has_video_info()) { + codec = media_info.video_info().codec(); + } else if (media_info.has_audio_info()) { + codec = media_info.audio_info().codec(); + } else if (media_info.has_text_info()) { + codec = media_info.text_info().codec(); + } + // Convert, for example, "mp4a.40.2" to simply "mp4a". + // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. + size_t dot = codec.find('.'); + if (dot != std::string::npos) { + codec.erase(dot); + } + return codec; +} + +std::string GetAdaptationSetKey(const MediaInfo& media_info, + bool ignore_codec) { + std::string key; + + if (media_info.has_video_info()) { + key.append("video:"); + } else if (media_info.has_audio_info()) { + key.append("audio:"); + } else if (media_info.has_text_info()) { + key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); + key.append(":"); + } else { + key.append("unknown:"); + } + + key.append(MediaInfo_ContainerType_Name(media_info.container_type())); + if (!ignore_codec) { + key.append(":"); + key.append(GetBaseCodec(media_info)); + + if (media_info.video_info().has_transfer_characteristics()) { + key.append(":"); + key.append( + std::to_string(media_info.video_info().transfer_characteristics())); + } else if (GetBaseCodec(media_info).find("dvh") == 0) { + // Dolby Vision (dvh1 or dvhe) is always HDR. + key.append(":"); + key.append(std::to_string(kTransferFunctionPQ)); + } + } + key.append(":"); + key.append(GetLanguage(media_info)); + + // Trick play streams of the same original stream, but possibly with + // different trick_play_factors, belong to the same trick play AdaptationSet. + if (media_info.video_info().has_playback_rate()) { + key.append(":trick_play"); + } + + if (!media_info.dash_accessibilities().empty()) { + key.append(":accessibility_"); + for (const std::string& accessibility : media_info.dash_accessibilities()) + key.append(accessibility); + } + + if (!media_info.dash_roles().empty()) { + key.append(":roles_"); + for (const std::string& role : media_info.dash_roles()) + key.append(role); + } + + return key; +} + +std::string SecondsToXmlDuration(double seconds) { + // Chrome internally uses time accurate to microseconds, which is implemented + // per MSE spec (https://www.w3.org/TR/media-source/). + // We need a string formatter that has at least microseconds accuracy for a + // normal video (with duration up to 3 hours). Chrome's DoubleToString + // implementation meets the requirement. + return "PT" + base::DoubleToString(seconds) + "S"; +} + +bool GetDurationAttribute(xmlNodePtr node, float* duration) { + DCHECK(node); + DCHECK(duration); + static const char kDuration[] = "duration"; + xml::scoped_xml_ptr duration_value( + xmlGetProp(node, BAD_CAST kDuration)); + + if (!duration_value) + return false; + + double duration_double_precision = 0.0; + if (!base::StringToDouble(reinterpret_cast(duration_value.get()), + &duration_double_precision)) { + return false; + } + + *duration = static_cast(duration_double_precision); + return true; +} + +bool MoreThanOneTrue(bool b1, bool b2, bool b3) { + return (b1 && b2) || (b2 && b3) || (b3 && b1); +} + +bool AtLeastOneTrue(bool b1, bool b2, bool b3) { + return b1 || b2 || b3; +} + +bool OnlyOneTrue(bool b1, bool b2, bool b3) { + return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); +} + +// Coverts binary data into human readable UUID format. +bool HexToUUID(const std::string& data, std::string* uuid_format) { + DCHECK(uuid_format); + const size_t kExpectedUUIDSize = 16; + if (data.size() != kExpectedUUIDSize) { + LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize + << " but is " << data.size() << " and the data in hex is " + << base::HexEncode(data.data(), data.size()); + return false; + } + + const std::string hex_encoded = + base::ToLowerASCII(base::HexEncode(data.data(), data.size())); + DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); + base::StringPiece all(hex_encoded); + // Note UUID has 5 parts separated with dashes. + // e.g. 123e4567-e89b-12d3-a456-426655440000 + // These StringPieces have each part. + base::StringPiece first = all.substr(0, 8); + base::StringPiece second = all.substr(8, 4); + base::StringPiece third = all.substr(12, 4); + base::StringPiece fourth = all.substr(16, 4); + base::StringPiece fifth = all.substr(20, 12); + + // 32 hexadecimal characters with 4 hyphens. + const size_t kHumanReadableUUIDSize = 36; + uuid_format->reserve(kHumanReadableUUIDSize); + first.CopyToString(uuid_format); + uuid_format->append("-"); + second.AppendToString(uuid_format); + uuid_format->append("-"); + third.AppendToString(uuid_format); + uuid_format->append("-"); + fourth.AppendToString(uuid_format); + uuid_format->append("-"); + fifth.AppendToString(uuid_format); + return true; +} + +void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, + const std::string& pssh, + std::list* content_protection_elements) { + const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; + for (std::list::iterator protection = + content_protection_elements->begin(); + protection != content_protection_elements->end(); ++protection) { + if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { + continue; + } + + for (std::vector::iterator subelement = + protection->subelements.begin(); + subelement != protection->subelements.end(); ++subelement) { + if (subelement->name == kPsshElementName) { + // For now, we want to remove the PSSH element because some players do + // not support updating pssh. + protection->subelements.erase(subelement); + + // TODO(rkuroiwa): Uncomment this and remove the line above when + // shaka-player supports updating PSSH. + // subelement->content = pssh; + return; + } + } + + // Reaching here means does not exist under the + // ContentProtection element. Add it. + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // protection->subelements.push_back(cenc_pssh); + return; + } + + // Reaching here means that ContentProtection for the DRM does not exist. + // Add it. + ContentProtectionElement content_protection; + content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // content_protection.subelements.push_back(cenc_pssh); + content_protection_elements->push_back(content_protection); + return; +} + +namespace { + +// UUID for Marlin Adaptive Streaming Specification – Simple Profile from +// https://dashif.org/identifiers/content_protection/. +const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; +// Unofficial FairPlay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; +// String representation of media::kPlayReadySystemId. +const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; // It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". -// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. -const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; - -Element GenerateMarlinContentIds(const std::string& key_id) { - // See https://github.com/shaka-project/shaka-packager/issues/381 for details. - static const char kMarlinContentIdName[] = "mas:MarlinContentId"; - static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; - static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; - - Element marlin_content_id; - marlin_content_id.name = kMarlinContentIdName; - marlin_content_id.content = - kMarlinContentIdPrefix + - base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); - - Element marlin_content_ids; - marlin_content_ids.name = kMarlinContentIdsName; - marlin_content_ids.subelements.push_back(marlin_content_id); - - return marlin_content_ids; -} - -Element GenerateCencPsshElement(const std::string& pssh) { - std::string base64_encoded_pssh; - base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), - &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = base64_encoded_pssh; - return cenc_pssh; -} - -// Extract MS PlayReady Object from given PSSH -// and encode it in base64. -Element GenerateMsprProElement(const std::string& pssh) { - std::unique_ptr b = - media::PsshBoxBuilder::ParseFromBox( - reinterpret_cast(pssh.data()), - pssh.size() - ); - - const std::vector *p_pssh = &b->pssh_data(); - std::string base64_encoded_mspr; - base::Base64Encode( - base::StringPiece( - reinterpret_cast(p_pssh->data()), - p_pssh->size()), - &base64_encoded_mspr - ); - Element mspr_pro; - mspr_pro.name = kMsproElementName; - mspr_pro.content = base64_encoded_mspr; - return mspr_pro; -} - -// Helper function. This works because Representation and AdaptationSet both -// have AddContentProtectionElement(). -template -void AddContentProtectionElementsHelperTemplated( - const MediaInfo& media_info, - ContentProtectionParent* parent) { - DCHECK(parent); - if (!media_info.has_protected_content()) - return; - - const MediaInfo::ProtectedContent& protected_content = - media_info.protected_content(); - - // DASH MPD spec specifies a default ContentProtection element for ISO BMFF - // (MP4) files. - const bool is_mp4_container = - media_info.container_type() == MediaInfo::CONTAINER_MP4; - std::string key_id_uuid_format; - if (protected_content.has_default_key_id() && - !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { - if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { - LOG(ERROR) << "Failed to convert default key ID into UUID format."; - } - } - - if (is_mp4_container) { - ContentProtectionElement mp4_content_protection; - mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; - mp4_content_protection.value = protected_content.protection_scheme(); - if (!key_id_uuid_format.empty()) { - mp4_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(mp4_content_protection); - } - - for (const auto& entry : protected_content.content_protection_entry()) { - if (!entry.has_uuid()) { - LOG(WARNING) - << "ContentProtectionEntry was specified but no UUID is set for " - << entry.name_version() << ", skipping."; - continue; - } - - ContentProtectionElement drm_content_protection; - - if (entry.has_name_version()) - drm_content_protection.value = entry.name_version(); - - if (entry.uuid() == kFairPlayUUID) { - VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " - "not support DASH signaling."; - continue; - } else if (entry.uuid() == kMarlinUUID) { - // Marlin requires its uuid to be in upper case. See #525 for details. - drm_content_protection.scheme_id_uri = - "urn:uuid:" + base::ToUpperASCII(entry.uuid()); - drm_content_protection.subelements.push_back( - GenerateMarlinContentIds(protected_content.default_key_id())); - } else { - drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); - if (!entry.pssh().empty()) { - drm_content_protection.subelements.push_back( - GenerateCencPsshElement(entry.pssh())); - if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { - drm_content_protection.subelements.push_back( - GenerateMsprProElement(entry.pssh())); - drm_content_protection.value = kContentProtectionValueMSPR20; - } - } - } - - if (!key_id_uuid_format.empty() && !is_mp4_container) { - drm_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(drm_content_protection); - } - - VLOG_IF(1, protected_content.content_protection_entry().size() == 0) - << "The media is encrypted but no content protection specified (can " - "happen with key rotation)."; -} -} // namespace - -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -} // namespace shaka +// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; + +Element GenerateMarlinContentIds(const std::string& key_id) { + // See https://github.com/shaka-project/shaka-packager/issues/381 for details. + static const char kMarlinContentIdName[] = "mas:MarlinContentId"; + static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; + static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; + + Element marlin_content_id; + marlin_content_id.name = kMarlinContentIdName; + marlin_content_id.content = + kMarlinContentIdPrefix + + base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); + + Element marlin_content_ids; + marlin_content_ids.name = kMarlinContentIdsName; + marlin_content_ids.subelements.push_back(marlin_content_id); + + return marlin_content_ids; +} + +Element GenerateCencPsshElement(const std::string& pssh) { + std::string base64_encoded_pssh; + base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), + &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + return cenc_pssh; +} + +// Extract MS PlayReady Object from given PSSH +// and encode it in base64. +Element GenerateMsprProElement(const std::string& pssh) { + std::unique_ptr b = + media::PsshBoxBuilder::ParseFromBox( + reinterpret_cast(pssh.data()), + pssh.size() + ); + + const std::vector *p_pssh = &b->pssh_data(); + std::string base64_encoded_mspr; + base::Base64Encode( + base::StringPiece( + reinterpret_cast(p_pssh->data()), + p_pssh->size()), + &base64_encoded_mspr + ); + Element mspr_pro; + mspr_pro.name = kMsproElementName; + mspr_pro.content = base64_encoded_mspr; + return mspr_pro; +} + +// Helper function. This works because Representation and AdaptationSet both +// have AddContentProtectionElement(). +template +void AddContentProtectionElementsHelperTemplated( + const MediaInfo& media_info, + ContentProtectionParent* parent) { + DCHECK(parent); + if (!media_info.has_protected_content()) + return; + + const MediaInfo::ProtectedContent& protected_content = + media_info.protected_content(); + + // DASH MPD spec specifies a default ContentProtection element for ISO BMFF + // (MP4) files. + const bool is_mp4_container = + media_info.container_type() == MediaInfo::CONTAINER_MP4; + std::string key_id_uuid_format; + if (protected_content.has_default_key_id() && + !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { + if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { + LOG(ERROR) << "Failed to convert default key ID into UUID format."; + } + } + + if (is_mp4_container) { + ContentProtectionElement mp4_content_protection; + mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; + mp4_content_protection.value = protected_content.protection_scheme(); + if (!key_id_uuid_format.empty()) { + mp4_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(mp4_content_protection); + } + + for (const auto& entry : protected_content.content_protection_entry()) { + if (!entry.has_uuid()) { + LOG(WARNING) + << "ContentProtectionEntry was specified but no UUID is set for " + << entry.name_version() << ", skipping."; + continue; + } + + ContentProtectionElement drm_content_protection; + + if (entry.has_name_version()) + drm_content_protection.value = entry.name_version(); + + if (entry.uuid() == kFairPlayUUID) { + VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " + "not support DASH signaling."; + continue; + } else if (entry.uuid() == kMarlinUUID) { + // Marlin requires its uuid to be in upper case. See #525 for details. + drm_content_protection.scheme_id_uri = + "urn:uuid:" + base::ToUpperASCII(entry.uuid()); + drm_content_protection.subelements.push_back( + GenerateMarlinContentIds(protected_content.default_key_id())); + } else { + drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (!entry.pssh().empty()) { + drm_content_protection.subelements.push_back( + GenerateCencPsshElement(entry.pssh())); + if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { + drm_content_protection.subelements.push_back( + GenerateMsprProElement(entry.pssh())); + drm_content_protection.value = kContentProtectionValueMSPR20; + } + } + } + + if (!key_id_uuid_format.empty() && !is_mp4_container) { + drm_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(drm_content_protection); + } + + VLOG_IF(1, protected_content.content_protection_entry().size() == 0) + << "The media is encrypted but no content protection specified (can " + "happen with key rotation)."; +} +} // namespace + +void AddContentProtectionElements(const MediaInfo& media_info, + Representation* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +void AddContentProtectionElements(const MediaInfo& media_info, + AdaptationSet* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +} // namespace shaka From 0f41665ae07e146a286a109ae766726b89ebc1d1 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 24 Jul 2023 15:10:27 -0700 Subject: [PATCH 04/11] Fix line endings --- packager/mpd/base/mpd_utils.cc | 986 ++++++++++++++++----------------- 1 file changed, 493 insertions(+), 493 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index df5ec4c24a8..4dcf11c894e 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -1,493 +1,493 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include "packager/mpd/base/mpd_utils.h" - -#include -#include - -#include "packager/base/base64.h" -#include "packager/base/logging.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/base/strings/string_util.h" -#include "packager/media/base/language_utils.h" -#include "packager/media/base/protection_system_specific_info.h" -#include "packager/mpd/base/adaptation_set.h" -#include "packager/mpd/base/content_protection_element.h" -#include "packager/mpd/base/representation.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - -DEFINE_bool( - use_legacy_vp9_codec_string, - false, - "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " - "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " - "in https://github.com/shaka-project/shaka-packager/issues/406, all major " - "browsers and platforms already support the new 'vp09' codec string."); - -namespace shaka { -namespace { - -bool IsKeyRotationDefaultKeyId(const std::string& key_id) { - for (char c : key_id) { - if (c != '\0') - return false; - } - return true; -} - -std::string TextCodecString(const MediaInfo& media_info) { - CHECK(media_info.has_text_info()); - const auto container_type = media_info.container_type(); - - // Codecs are not needed when mimeType is "text/*". Having a codec would be - // redundant. - if (container_type == MediaInfo::CONTAINER_TEXT) { - return ""; - } - - // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override - // the default codec value. - const std::string& codec = media_info.text_info().codec(); - if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { - return "stpp"; - } - - return codec; -} - -} // namespace - -bool HasVODOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_range() || media_info.has_index_range() || - media_info.has_media_file_url(); -} - -bool HasLiveOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_segment_url() || - media_info.has_segment_template_url(); -} - -void RemoveDuplicateAttributes( - ContentProtectionElement* content_protection_element) { - DCHECK(content_protection_element); - typedef std::map AttributesMap; - - AttributesMap& attributes = content_protection_element->additional_attributes; - if (!content_protection_element->value.empty()) - attributes.erase("value"); - - if (!content_protection_element->scheme_id_uri.empty()) - attributes.erase("schemeIdUri"); -} - -std::string GetLanguage(const MediaInfo& media_info) { - std::string lang; - if (media_info.has_audio_info()) { - lang = media_info.audio_info().language(); - } else if (media_info.has_text_info()) { - lang = media_info.text_info().language(); - } - return LanguageToShortestForm(lang); -} - -std::string GetCodecs(const MediaInfo& media_info) { - CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), - media_info.has_text_info())); - - if (media_info.has_video_info()) { - if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { - std::string codec = media_info.video_info().codec().substr(0, 4); - // media_info.video_info().codec() contains new revised codec string - // specified by "VPx in ISO BMFF" document, which is not compatible to - // old codec strings in WebM. Hack it here before all browsers support - // new codec strings. - if (codec == "vp08") - return "vp8"; - if (FLAGS_use_legacy_vp9_codec_string) { - if (codec == "vp09") - return "vp9"; - } - } - return media_info.video_info().codec(); - } - - if (media_info.has_audio_info()) - return media_info.audio_info().codec(); - - if (media_info.has_text_info()) - return TextCodecString(media_info); - - NOTREACHED(); - return ""; -} - -std::string GetBaseCodec(const MediaInfo& media_info) { - std::string codec; - if (media_info.has_video_info()) { - codec = media_info.video_info().codec(); - } else if (media_info.has_audio_info()) { - codec = media_info.audio_info().codec(); - } else if (media_info.has_text_info()) { - codec = media_info.text_info().codec(); - } - // Convert, for example, "mp4a.40.2" to simply "mp4a". - // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. - size_t dot = codec.find('.'); - if (dot != std::string::npos) { - codec.erase(dot); - } - return codec; -} - -std::string GetAdaptationSetKey(const MediaInfo& media_info, - bool ignore_codec) { - std::string key; - - if (media_info.has_video_info()) { - key.append("video:"); - } else if (media_info.has_audio_info()) { - key.append("audio:"); - } else if (media_info.has_text_info()) { - key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); - key.append(":"); - } else { - key.append("unknown:"); - } - - key.append(MediaInfo_ContainerType_Name(media_info.container_type())); - if (!ignore_codec) { - key.append(":"); - key.append(GetBaseCodec(media_info)); - - if (media_info.video_info().has_transfer_characteristics()) { - key.append(":"); - key.append( - std::to_string(media_info.video_info().transfer_characteristics())); - } else if (GetBaseCodec(media_info).find("dvh") == 0) { - // Dolby Vision (dvh1 or dvhe) is always HDR. - key.append(":"); - key.append(std::to_string(kTransferFunctionPQ)); - } - } - key.append(":"); - key.append(GetLanguage(media_info)); - - // Trick play streams of the same original stream, but possibly with - // different trick_play_factors, belong to the same trick play AdaptationSet. - if (media_info.video_info().has_playback_rate()) { - key.append(":trick_play"); - } - - if (!media_info.dash_accessibilities().empty()) { - key.append(":accessibility_"); - for (const std::string& accessibility : media_info.dash_accessibilities()) - key.append(accessibility); - } - - if (!media_info.dash_roles().empty()) { - key.append(":roles_"); - for (const std::string& role : media_info.dash_roles()) - key.append(role); - } - - return key; -} - -std::string SecondsToXmlDuration(double seconds) { - // Chrome internally uses time accurate to microseconds, which is implemented - // per MSE spec (https://www.w3.org/TR/media-source/). - // We need a string formatter that has at least microseconds accuracy for a - // normal video (with duration up to 3 hours). Chrome's DoubleToString - // implementation meets the requirement. - return "PT" + base::DoubleToString(seconds) + "S"; -} - -bool GetDurationAttribute(xmlNodePtr node, float* duration) { - DCHECK(node); - DCHECK(duration); - static const char kDuration[] = "duration"; - xml::scoped_xml_ptr duration_value( - xmlGetProp(node, BAD_CAST kDuration)); - - if (!duration_value) - return false; - - double duration_double_precision = 0.0; - if (!base::StringToDouble(reinterpret_cast(duration_value.get()), - &duration_double_precision)) { - return false; - } - - *duration = static_cast(duration_double_precision); - return true; -} - -bool MoreThanOneTrue(bool b1, bool b2, bool b3) { - return (b1 && b2) || (b2 && b3) || (b3 && b1); -} - -bool AtLeastOneTrue(bool b1, bool b2, bool b3) { - return b1 || b2 || b3; -} - -bool OnlyOneTrue(bool b1, bool b2, bool b3) { - return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); -} - -// Coverts binary data into human readable UUID format. -bool HexToUUID(const std::string& data, std::string* uuid_format) { - DCHECK(uuid_format); - const size_t kExpectedUUIDSize = 16; - if (data.size() != kExpectedUUIDSize) { - LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize - << " but is " << data.size() << " and the data in hex is " - << base::HexEncode(data.data(), data.size()); - return false; - } - - const std::string hex_encoded = - base::ToLowerASCII(base::HexEncode(data.data(), data.size())); - DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); - base::StringPiece all(hex_encoded); - // Note UUID has 5 parts separated with dashes. - // e.g. 123e4567-e89b-12d3-a456-426655440000 - // These StringPieces have each part. - base::StringPiece first = all.substr(0, 8); - base::StringPiece second = all.substr(8, 4); - base::StringPiece third = all.substr(12, 4); - base::StringPiece fourth = all.substr(16, 4); - base::StringPiece fifth = all.substr(20, 12); - - // 32 hexadecimal characters with 4 hyphens. - const size_t kHumanReadableUUIDSize = 36; - uuid_format->reserve(kHumanReadableUUIDSize); - first.CopyToString(uuid_format); - uuid_format->append("-"); - second.AppendToString(uuid_format); - uuid_format->append("-"); - third.AppendToString(uuid_format); - uuid_format->append("-"); - fourth.AppendToString(uuid_format); - uuid_format->append("-"); - fifth.AppendToString(uuid_format); - return true; -} - -void UpdateContentProtectionPsshHelper( - const std::string& drm_uuid, - const std::string& pssh, - std::list* content_protection_elements) { - const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; - for (std::list::iterator protection = - content_protection_elements->begin(); - protection != content_protection_elements->end(); ++protection) { - if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { - continue; - } - - for (std::vector::iterator subelement = - protection->subelements.begin(); - subelement != protection->subelements.end(); ++subelement) { - if (subelement->name == kPsshElementName) { - // For now, we want to remove the PSSH element because some players do - // not support updating pssh. - protection->subelements.erase(subelement); - - // TODO(rkuroiwa): Uncomment this and remove the line above when - // shaka-player supports updating PSSH. - // subelement->content = pssh; - return; - } - } - - // Reaching here means does not exist under the - // ContentProtection element. Add it. - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // protection->subelements.push_back(cenc_pssh); - return; - } - - // Reaching here means that ContentProtection for the DRM does not exist. - // Add it. - ContentProtectionElement content_protection; - content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // content_protection.subelements.push_back(cenc_pssh); - content_protection_elements->push_back(content_protection); - return; -} - -namespace { - -// UUID for Marlin Adaptive Streaming Specification – Simple Profile from -// https://dashif.org/identifiers/content_protection/. -const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; -// Unofficial FairPlay system id extracted from -// https://forums.developer.apple.com/thread/6185. -const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; -// String representation of media::kPlayReadySystemId. -const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; -// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". -// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. -const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; - -Element GenerateMarlinContentIds(const std::string& key_id) { - // See https://github.com/shaka-project/shaka-packager/issues/381 for details. - static const char kMarlinContentIdName[] = "mas:MarlinContentId"; - static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; - static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; - - Element marlin_content_id; - marlin_content_id.name = kMarlinContentIdName; - marlin_content_id.content = - kMarlinContentIdPrefix + - base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); - - Element marlin_content_ids; - marlin_content_ids.name = kMarlinContentIdsName; - marlin_content_ids.subelements.push_back(marlin_content_id); - - return marlin_content_ids; -} - -Element GenerateCencPsshElement(const std::string& pssh) { - std::string base64_encoded_pssh; - base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), - &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = base64_encoded_pssh; - return cenc_pssh; -} - -// Extract MS PlayReady Object from given PSSH -// and encode it in base64. -Element GenerateMsprProElement(const std::string& pssh) { - std::unique_ptr b = - media::PsshBoxBuilder::ParseFromBox( - reinterpret_cast(pssh.data()), - pssh.size() - ); - - const std::vector *p_pssh = &b->pssh_data(); - std::string base64_encoded_mspr; - base::Base64Encode( - base::StringPiece( - reinterpret_cast(p_pssh->data()), - p_pssh->size()), - &base64_encoded_mspr - ); - Element mspr_pro; - mspr_pro.name = kMsproElementName; - mspr_pro.content = base64_encoded_mspr; - return mspr_pro; -} - -// Helper function. This works because Representation and AdaptationSet both -// have AddContentProtectionElement(). -template -void AddContentProtectionElementsHelperTemplated( - const MediaInfo& media_info, - ContentProtectionParent* parent) { - DCHECK(parent); - if (!media_info.has_protected_content()) - return; - - const MediaInfo::ProtectedContent& protected_content = - media_info.protected_content(); - - // DASH MPD spec specifies a default ContentProtection element for ISO BMFF - // (MP4) files. - const bool is_mp4_container = - media_info.container_type() == MediaInfo::CONTAINER_MP4; - std::string key_id_uuid_format; - if (protected_content.has_default_key_id() && - !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { - if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { - LOG(ERROR) << "Failed to convert default key ID into UUID format."; - } - } - - if (is_mp4_container) { - ContentProtectionElement mp4_content_protection; - mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; - mp4_content_protection.value = protected_content.protection_scheme(); - if (!key_id_uuid_format.empty()) { - mp4_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(mp4_content_protection); - } - - for (const auto& entry : protected_content.content_protection_entry()) { - if (!entry.has_uuid()) { - LOG(WARNING) - << "ContentProtectionEntry was specified but no UUID is set for " - << entry.name_version() << ", skipping."; - continue; - } - - ContentProtectionElement drm_content_protection; - - if (entry.has_name_version()) - drm_content_protection.value = entry.name_version(); - - if (entry.uuid() == kFairPlayUUID) { - VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " - "not support DASH signaling."; - continue; - } else if (entry.uuid() == kMarlinUUID) { - // Marlin requires its uuid to be in upper case. See #525 for details. - drm_content_protection.scheme_id_uri = - "urn:uuid:" + base::ToUpperASCII(entry.uuid()); - drm_content_protection.subelements.push_back( - GenerateMarlinContentIds(protected_content.default_key_id())); - } else { - drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); - if (!entry.pssh().empty()) { - drm_content_protection.subelements.push_back( - GenerateCencPsshElement(entry.pssh())); - if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { - drm_content_protection.subelements.push_back( - GenerateMsprProElement(entry.pssh())); - drm_content_protection.value = kContentProtectionValueMSPR20; - } - } - } - - if (!key_id_uuid_format.empty() && !is_mp4_container) { - drm_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(drm_content_protection); - } - - VLOG_IF(1, protected_content.content_protection_entry().size() == 0) - << "The media is encrypted but no content protection specified (can " - "happen with key rotation)."; -} -} // namespace - -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -} // namespace shaka +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/mpd/base/mpd_utils.h" + +#include +#include + +#include "packager/base/base64.h" +#include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/string_util.h" +#include "packager/media/base/language_utils.h" +#include "packager/media/base/protection_system_specific_info.h" +#include "packager/mpd/base/adaptation_set.h" +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/representation.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + +DEFINE_bool( + use_legacy_vp9_codec_string, + false, + "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " + "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " + "in https://github.com/shaka-project/shaka-packager/issues/406, all major " + "browsers and platforms already support the new 'vp09' codec string."); + +namespace shaka { +namespace { + +bool IsKeyRotationDefaultKeyId(const std::string& key_id) { + for (char c : key_id) { + if (c != '\0') + return false; + } + return true; +} + +std::string TextCodecString(const MediaInfo& media_info) { + CHECK(media_info.has_text_info()); + const auto container_type = media_info.container_type(); + + // Codecs are not needed when mimeType is "text/*". Having a codec would be + // redundant. + if (container_type == MediaInfo::CONTAINER_TEXT) { + return ""; + } + + // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override + // the default codec value. + const std::string& codec = media_info.text_info().codec(); + if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { + return "stpp"; + } + + return codec; +} + +} // namespace + +bool HasVODOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_range() || media_info.has_index_range() || + media_info.has_media_file_url(); +} + +bool HasLiveOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_segment_url() || + media_info.has_segment_template_url(); +} + +void RemoveDuplicateAttributes( + ContentProtectionElement* content_protection_element) { + DCHECK(content_protection_element); + typedef std::map AttributesMap; + + AttributesMap& attributes = content_protection_element->additional_attributes; + if (!content_protection_element->value.empty()) + attributes.erase("value"); + + if (!content_protection_element->scheme_id_uri.empty()) + attributes.erase("schemeIdUri"); +} + +std::string GetLanguage(const MediaInfo& media_info) { + std::string lang; + if (media_info.has_audio_info()) { + lang = media_info.audio_info().language(); + } else if (media_info.has_text_info()) { + lang = media_info.text_info().language(); + } + return LanguageToShortestForm(lang); +} + +std::string GetCodecs(const MediaInfo& media_info) { + CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), + media_info.has_text_info())); + + if (media_info.has_video_info()) { + if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { + std::string codec = media_info.video_info().codec().substr(0, 4); + // media_info.video_info().codec() contains new revised codec string + // specified by "VPx in ISO BMFF" document, which is not compatible to + // old codec strings in WebM. Hack it here before all browsers support + // new codec strings. + if (codec == "vp08") + return "vp8"; + if (FLAGS_use_legacy_vp9_codec_string) { + if (codec == "vp09") + return "vp9"; + } + } + return media_info.video_info().codec(); + } + + if (media_info.has_audio_info()) + return media_info.audio_info().codec(); + + if (media_info.has_text_info()) + return TextCodecString(media_info); + + NOTREACHED(); + return ""; +} + +std::string GetBaseCodec(const MediaInfo& media_info) { + std::string codec; + if (media_info.has_video_info()) { + codec = media_info.video_info().codec(); + } else if (media_info.has_audio_info()) { + codec = media_info.audio_info().codec(); + } else if (media_info.has_text_info()) { + codec = media_info.text_info().codec(); + } + // Convert, for example, "mp4a.40.2" to simply "mp4a". + // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. + size_t dot = codec.find('.'); + if (dot != std::string::npos) { + codec.erase(dot); + } + return codec; +} + +std::string GetAdaptationSetKey(const MediaInfo& media_info, + bool ignore_codec) { + std::string key; + + if (media_info.has_video_info()) { + key.append("video:"); + } else if (media_info.has_audio_info()) { + key.append("audio:"); + } else if (media_info.has_text_info()) { + key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); + key.append(":"); + } else { + key.append("unknown:"); + } + + key.append(MediaInfo_ContainerType_Name(media_info.container_type())); + if (!ignore_codec) { + key.append(":"); + key.append(GetBaseCodec(media_info)); + + if (media_info.video_info().has_transfer_characteristics()) { + key.append(":"); + key.append( + std::to_string(media_info.video_info().transfer_characteristics())); + } else if (GetBaseCodec(media_info).find("dvh") == 0) { + // Dolby Vision (dvh1 or dvhe) is always HDR. + key.append(":"); + key.append(std::to_string(kTransferFunctionPQ)); + } + } + key.append(":"); + key.append(GetLanguage(media_info)); + + // Trick play streams of the same original stream, but possibly with + // different trick_play_factors, belong to the same trick play AdaptationSet. + if (media_info.video_info().has_playback_rate()) { + key.append(":trick_play"); + } + + if (!media_info.dash_accessibilities().empty()) { + key.append(":accessibility_"); + for (const std::string& accessibility : media_info.dash_accessibilities()) + key.append(accessibility); + } + + if (!media_info.dash_roles().empty()) { + key.append(":roles_"); + for (const std::string& role : media_info.dash_roles()) + key.append(role); + } + + return key; +} + +std::string SecondsToXmlDuration(double seconds) { + // Chrome internally uses time accurate to microseconds, which is implemented + // per MSE spec (https://www.w3.org/TR/media-source/). + // We need a string formatter that has at least microseconds accuracy for a + // normal video (with duration up to 3 hours). Chrome's DoubleToString + // implementation meets the requirement. + return "PT" + base::DoubleToString(seconds) + "S"; +} + +bool GetDurationAttribute(xmlNodePtr node, float* duration) { + DCHECK(node); + DCHECK(duration); + static const char kDuration[] = "duration"; + xml::scoped_xml_ptr duration_value( + xmlGetProp(node, BAD_CAST kDuration)); + + if (!duration_value) + return false; + + double duration_double_precision = 0.0; + if (!base::StringToDouble(reinterpret_cast(duration_value.get()), + &duration_double_precision)) { + return false; + } + + *duration = static_cast(duration_double_precision); + return true; +} + +bool MoreThanOneTrue(bool b1, bool b2, bool b3) { + return (b1 && b2) || (b2 && b3) || (b3 && b1); +} + +bool AtLeastOneTrue(bool b1, bool b2, bool b3) { + return b1 || b2 || b3; +} + +bool OnlyOneTrue(bool b1, bool b2, bool b3) { + return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); +} + +// Coverts binary data into human readable UUID format. +bool HexToUUID(const std::string& data, std::string* uuid_format) { + DCHECK(uuid_format); + const size_t kExpectedUUIDSize = 16; + if (data.size() != kExpectedUUIDSize) { + LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize + << " but is " << data.size() << " and the data in hex is " + << base::HexEncode(data.data(), data.size()); + return false; + } + + const std::string hex_encoded = + base::ToLowerASCII(base::HexEncode(data.data(), data.size())); + DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); + base::StringPiece all(hex_encoded); + // Note UUID has 5 parts separated with dashes. + // e.g. 123e4567-e89b-12d3-a456-426655440000 + // These StringPieces have each part. + base::StringPiece first = all.substr(0, 8); + base::StringPiece second = all.substr(8, 4); + base::StringPiece third = all.substr(12, 4); + base::StringPiece fourth = all.substr(16, 4); + base::StringPiece fifth = all.substr(20, 12); + + // 32 hexadecimal characters with 4 hyphens. + const size_t kHumanReadableUUIDSize = 36; + uuid_format->reserve(kHumanReadableUUIDSize); + first.CopyToString(uuid_format); + uuid_format->append("-"); + second.AppendToString(uuid_format); + uuid_format->append("-"); + third.AppendToString(uuid_format); + uuid_format->append("-"); + fourth.AppendToString(uuid_format); + uuid_format->append("-"); + fifth.AppendToString(uuid_format); + return true; +} + +void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, + const std::string& pssh, + std::list* content_protection_elements) { + const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; + for (std::list::iterator protection = + content_protection_elements->begin(); + protection != content_protection_elements->end(); ++protection) { + if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { + continue; + } + + for (std::vector::iterator subelement = + protection->subelements.begin(); + subelement != protection->subelements.end(); ++subelement) { + if (subelement->name == kPsshElementName) { + // For now, we want to remove the PSSH element because some players do + // not support updating pssh. + protection->subelements.erase(subelement); + + // TODO(rkuroiwa): Uncomment this and remove the line above when + // shaka-player supports updating PSSH. + // subelement->content = pssh; + return; + } + } + + // Reaching here means does not exist under the + // ContentProtection element. Add it. + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // protection->subelements.push_back(cenc_pssh); + return; + } + + // Reaching here means that ContentProtection for the DRM does not exist. + // Add it. + ContentProtectionElement content_protection; + content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // content_protection.subelements.push_back(cenc_pssh); + content_protection_elements->push_back(content_protection); + return; +} + +namespace { + +// UUID for Marlin Adaptive Streaming Specification – Simple Profile from +// https://dashif.org/identifiers/content_protection/. +const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; +// Unofficial FairPlay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; +// String representation of media::kPlayReadySystemId. +const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; +// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". +// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; + +Element GenerateMarlinContentIds(const std::string& key_id) { + // See https://github.com/shaka-project/shaka-packager/issues/381 for details. + static const char kMarlinContentIdName[] = "mas:MarlinContentId"; + static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; + static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; + + Element marlin_content_id; + marlin_content_id.name = kMarlinContentIdName; + marlin_content_id.content = + kMarlinContentIdPrefix + + base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); + + Element marlin_content_ids; + marlin_content_ids.name = kMarlinContentIdsName; + marlin_content_ids.subelements.push_back(marlin_content_id); + + return marlin_content_ids; +} + +Element GenerateCencPsshElement(const std::string& pssh) { + std::string base64_encoded_pssh; + base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), + &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + return cenc_pssh; +} + +// Extract MS PlayReady Object from given PSSH +// and encode it in base64. +Element GenerateMsprProElement(const std::string& pssh) { + std::unique_ptr b = + media::PsshBoxBuilder::ParseFromBox( + reinterpret_cast(pssh.data()), + pssh.size() + ); + + const std::vector *p_pssh = &b->pssh_data(); + std::string base64_encoded_mspr; + base::Base64Encode( + base::StringPiece( + reinterpret_cast(p_pssh->data()), + p_pssh->size()), + &base64_encoded_mspr + ); + Element mspr_pro; + mspr_pro.name = kMsproElementName; + mspr_pro.content = base64_encoded_mspr; + return mspr_pro; +} + +// Helper function. This works because Representation and AdaptationSet both +// have AddContentProtectionElement(). +template +void AddContentProtectionElementsHelperTemplated( + const MediaInfo& media_info, + ContentProtectionParent* parent) { + DCHECK(parent); + if (!media_info.has_protected_content()) + return; + + const MediaInfo::ProtectedContent& protected_content = + media_info.protected_content(); + + // DASH MPD spec specifies a default ContentProtection element for ISO BMFF + // (MP4) files. + const bool is_mp4_container = + media_info.container_type() == MediaInfo::CONTAINER_MP4; + std::string key_id_uuid_format; + if (protected_content.has_default_key_id() && + !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { + if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { + LOG(ERROR) << "Failed to convert default key ID into UUID format."; + } + } + + if (is_mp4_container) { + ContentProtectionElement mp4_content_protection; + mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; + mp4_content_protection.value = protected_content.protection_scheme(); + if (!key_id_uuid_format.empty()) { + mp4_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(mp4_content_protection); + } + + for (const auto& entry : protected_content.content_protection_entry()) { + if (!entry.has_uuid()) { + LOG(WARNING) + << "ContentProtectionEntry was specified but no UUID is set for " + << entry.name_version() << ", skipping."; + continue; + } + + ContentProtectionElement drm_content_protection; + + if (entry.has_name_version()) + drm_content_protection.value = entry.name_version(); + + if (entry.uuid() == kFairPlayUUID) { + VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " + "not support DASH signaling."; + continue; + } else if (entry.uuid() == kMarlinUUID) { + // Marlin requires its uuid to be in upper case. See #525 for details. + drm_content_protection.scheme_id_uri = + "urn:uuid:" + base::ToUpperASCII(entry.uuid()); + drm_content_protection.subelements.push_back( + GenerateMarlinContentIds(protected_content.default_key_id())); + } else { + drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (!entry.pssh().empty()) { + drm_content_protection.subelements.push_back( + GenerateCencPsshElement(entry.pssh())); + if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { + drm_content_protection.subelements.push_back( + GenerateMsprProElement(entry.pssh())); + drm_content_protection.value = kContentProtectionValueMSPR20; + } + } + } + + if (!key_id_uuid_format.empty() && !is_mp4_container) { + drm_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(drm_content_protection); + } + + VLOG_IF(1, protected_content.content_protection_entry().size() == 0) + << "The media is encrypted but no content protection specified (can " + "happen with key rotation)."; +} +} // namespace + +void AddContentProtectionElements(const MediaInfo& media_info, + Representation* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +void AddContentProtectionElements(const MediaInfo& media_info, + AdaptationSet* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +} // namespace shaka From 144f237a18d6310ef98c8c4ecc1af55e993be86b Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 24 Jul 2023 15:13:14 -0700 Subject: [PATCH 05/11] Flip Dolby special case order --- packager/mpd/base/period.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index a2f2f99d9fe..097d53a4049 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -276,11 +276,12 @@ bool Period::SetNewAdaptationSetAttributes( // - Common CCIP values. // Dolby vision: // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH - if (new_adaptation_set->codec().find("dvh") == 0) { - new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); - } else if (media_info.video_info().has_transfer_characteristics()) { + if (media_info.video_info().has_transfer_characteristics()) { new_adaptation_set->set_transfer_characteristics( media_info.video_info().transfer_characteristics()); + } else if (new_adaptation_set->codec().find("dvh") == 0) { + // Dolby Vision (dvh1 or dvhe) is always HDR. + new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); } } else if (media_info.has_text_info()) { From 85115519a656fedaabbb74b44081d79b7f202c5b Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Mon, 24 Jul 2023 15:14:38 -0700 Subject: [PATCH 06/11] Fix linter --- packager/mpd/base/mpd_utils.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 4dcf11c894e..9309991ee0b 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -337,8 +337,9 @@ const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; // String representation of media::kPlayReadySystemId. const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; -// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". -// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +// It is RECOMMENDED to include the @value attribute with name and version +// "MSPR 2.0". See +// https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; Element GenerateMarlinContentIds(const std::string& key_id) { From 2bfd6ef341614dc5b74a0a23d6d46deff6251f1b Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 1 Sep 2023 12:05:51 -0700 Subject: [PATCH 07/11] Revert "Fix linter" This reverts commit 77fbcb16d25e69439b0ead6a4aaab29219a08536. --- packager/mpd/base/mpd_utils.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 9309991ee0b..4dcf11c894e 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -337,9 +337,8 @@ const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; // String representation of media::kPlayReadySystemId. const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; -// It is RECOMMENDED to include the @value attribute with name and version -// "MSPR 2.0". See -// https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". +// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; Element GenerateMarlinContentIds(const std::string& key_id) { From bb3d188b805807baa81869501133762905c4215d Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 1 Sep 2023 12:05:51 -0700 Subject: [PATCH 08/11] Revert "Flip Dolby special case order" This reverts commit 00f2890566b71c4c143bccbc3c37e8da1a1f8d23. --- packager/mpd/base/period.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index 097d53a4049..a2f2f99d9fe 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -276,12 +276,11 @@ bool Period::SetNewAdaptationSetAttributes( // - Common CCIP values. // Dolby vision: // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH - if (media_info.video_info().has_transfer_characteristics()) { + if (new_adaptation_set->codec().find("dvh") == 0) { + new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); + } else if (media_info.video_info().has_transfer_characteristics()) { new_adaptation_set->set_transfer_characteristics( media_info.video_info().transfer_characteristics()); - } else if (new_adaptation_set->codec().find("dvh") == 0) { - // Dolby Vision (dvh1 or dvhe) is always HDR. - new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); } } else if (media_info.has_text_info()) { From 4c58a1ab5189e822e6848ccdbd4096b7dfa38b27 Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 1 Sep 2023 12:05:51 -0700 Subject: [PATCH 09/11] Revert "Fix line endings" This reverts commit 783f70b8a114fa7af81e417795b3ea2cba2bdcbe. --- packager/mpd/base/mpd_utils.cc | 986 ++++++++++++++++----------------- 1 file changed, 493 insertions(+), 493 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 4dcf11c894e..df5ec4c24a8 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -1,493 +1,493 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include "packager/mpd/base/mpd_utils.h" - -#include -#include - -#include "packager/base/base64.h" -#include "packager/base/logging.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/base/strings/string_util.h" -#include "packager/media/base/language_utils.h" -#include "packager/media/base/protection_system_specific_info.h" -#include "packager/mpd/base/adaptation_set.h" -#include "packager/mpd/base/content_protection_element.h" -#include "packager/mpd/base/representation.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - -DEFINE_bool( - use_legacy_vp9_codec_string, - false, - "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " - "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " - "in https://github.com/shaka-project/shaka-packager/issues/406, all major " - "browsers and platforms already support the new 'vp09' codec string."); - -namespace shaka { -namespace { - -bool IsKeyRotationDefaultKeyId(const std::string& key_id) { - for (char c : key_id) { - if (c != '\0') - return false; - } - return true; -} - -std::string TextCodecString(const MediaInfo& media_info) { - CHECK(media_info.has_text_info()); - const auto container_type = media_info.container_type(); - - // Codecs are not needed when mimeType is "text/*". Having a codec would be - // redundant. - if (container_type == MediaInfo::CONTAINER_TEXT) { - return ""; - } - - // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override - // the default codec value. - const std::string& codec = media_info.text_info().codec(); - if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { - return "stpp"; - } - - return codec; -} - -} // namespace - -bool HasVODOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_range() || media_info.has_index_range() || - media_info.has_media_file_url(); -} - -bool HasLiveOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_segment_url() || - media_info.has_segment_template_url(); -} - -void RemoveDuplicateAttributes( - ContentProtectionElement* content_protection_element) { - DCHECK(content_protection_element); - typedef std::map AttributesMap; - - AttributesMap& attributes = content_protection_element->additional_attributes; - if (!content_protection_element->value.empty()) - attributes.erase("value"); - - if (!content_protection_element->scheme_id_uri.empty()) - attributes.erase("schemeIdUri"); -} - -std::string GetLanguage(const MediaInfo& media_info) { - std::string lang; - if (media_info.has_audio_info()) { - lang = media_info.audio_info().language(); - } else if (media_info.has_text_info()) { - lang = media_info.text_info().language(); - } - return LanguageToShortestForm(lang); -} - -std::string GetCodecs(const MediaInfo& media_info) { - CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), - media_info.has_text_info())); - - if (media_info.has_video_info()) { - if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { - std::string codec = media_info.video_info().codec().substr(0, 4); - // media_info.video_info().codec() contains new revised codec string - // specified by "VPx in ISO BMFF" document, which is not compatible to - // old codec strings in WebM. Hack it here before all browsers support - // new codec strings. - if (codec == "vp08") - return "vp8"; - if (FLAGS_use_legacy_vp9_codec_string) { - if (codec == "vp09") - return "vp9"; - } - } - return media_info.video_info().codec(); - } - - if (media_info.has_audio_info()) - return media_info.audio_info().codec(); - - if (media_info.has_text_info()) - return TextCodecString(media_info); - - NOTREACHED(); - return ""; -} - -std::string GetBaseCodec(const MediaInfo& media_info) { - std::string codec; - if (media_info.has_video_info()) { - codec = media_info.video_info().codec(); - } else if (media_info.has_audio_info()) { - codec = media_info.audio_info().codec(); - } else if (media_info.has_text_info()) { - codec = media_info.text_info().codec(); - } - // Convert, for example, "mp4a.40.2" to simply "mp4a". - // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. - size_t dot = codec.find('.'); - if (dot != std::string::npos) { - codec.erase(dot); - } - return codec; -} - -std::string GetAdaptationSetKey(const MediaInfo& media_info, - bool ignore_codec) { - std::string key; - - if (media_info.has_video_info()) { - key.append("video:"); - } else if (media_info.has_audio_info()) { - key.append("audio:"); - } else if (media_info.has_text_info()) { - key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); - key.append(":"); - } else { - key.append("unknown:"); - } - - key.append(MediaInfo_ContainerType_Name(media_info.container_type())); - if (!ignore_codec) { - key.append(":"); - key.append(GetBaseCodec(media_info)); - - if (media_info.video_info().has_transfer_characteristics()) { - key.append(":"); - key.append( - std::to_string(media_info.video_info().transfer_characteristics())); - } else if (GetBaseCodec(media_info).find("dvh") == 0) { - // Dolby Vision (dvh1 or dvhe) is always HDR. - key.append(":"); - key.append(std::to_string(kTransferFunctionPQ)); - } - } - key.append(":"); - key.append(GetLanguage(media_info)); - - // Trick play streams of the same original stream, but possibly with - // different trick_play_factors, belong to the same trick play AdaptationSet. - if (media_info.video_info().has_playback_rate()) { - key.append(":trick_play"); - } - - if (!media_info.dash_accessibilities().empty()) { - key.append(":accessibility_"); - for (const std::string& accessibility : media_info.dash_accessibilities()) - key.append(accessibility); - } - - if (!media_info.dash_roles().empty()) { - key.append(":roles_"); - for (const std::string& role : media_info.dash_roles()) - key.append(role); - } - - return key; -} - -std::string SecondsToXmlDuration(double seconds) { - // Chrome internally uses time accurate to microseconds, which is implemented - // per MSE spec (https://www.w3.org/TR/media-source/). - // We need a string formatter that has at least microseconds accuracy for a - // normal video (with duration up to 3 hours). Chrome's DoubleToString - // implementation meets the requirement. - return "PT" + base::DoubleToString(seconds) + "S"; -} - -bool GetDurationAttribute(xmlNodePtr node, float* duration) { - DCHECK(node); - DCHECK(duration); - static const char kDuration[] = "duration"; - xml::scoped_xml_ptr duration_value( - xmlGetProp(node, BAD_CAST kDuration)); - - if (!duration_value) - return false; - - double duration_double_precision = 0.0; - if (!base::StringToDouble(reinterpret_cast(duration_value.get()), - &duration_double_precision)) { - return false; - } - - *duration = static_cast(duration_double_precision); - return true; -} - -bool MoreThanOneTrue(bool b1, bool b2, bool b3) { - return (b1 && b2) || (b2 && b3) || (b3 && b1); -} - -bool AtLeastOneTrue(bool b1, bool b2, bool b3) { - return b1 || b2 || b3; -} - -bool OnlyOneTrue(bool b1, bool b2, bool b3) { - return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); -} - -// Coverts binary data into human readable UUID format. -bool HexToUUID(const std::string& data, std::string* uuid_format) { - DCHECK(uuid_format); - const size_t kExpectedUUIDSize = 16; - if (data.size() != kExpectedUUIDSize) { - LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize - << " but is " << data.size() << " and the data in hex is " - << base::HexEncode(data.data(), data.size()); - return false; - } - - const std::string hex_encoded = - base::ToLowerASCII(base::HexEncode(data.data(), data.size())); - DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); - base::StringPiece all(hex_encoded); - // Note UUID has 5 parts separated with dashes. - // e.g. 123e4567-e89b-12d3-a456-426655440000 - // These StringPieces have each part. - base::StringPiece first = all.substr(0, 8); - base::StringPiece second = all.substr(8, 4); - base::StringPiece third = all.substr(12, 4); - base::StringPiece fourth = all.substr(16, 4); - base::StringPiece fifth = all.substr(20, 12); - - // 32 hexadecimal characters with 4 hyphens. - const size_t kHumanReadableUUIDSize = 36; - uuid_format->reserve(kHumanReadableUUIDSize); - first.CopyToString(uuid_format); - uuid_format->append("-"); - second.AppendToString(uuid_format); - uuid_format->append("-"); - third.AppendToString(uuid_format); - uuid_format->append("-"); - fourth.AppendToString(uuid_format); - uuid_format->append("-"); - fifth.AppendToString(uuid_format); - return true; -} - -void UpdateContentProtectionPsshHelper( - const std::string& drm_uuid, - const std::string& pssh, - std::list* content_protection_elements) { - const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; - for (std::list::iterator protection = - content_protection_elements->begin(); - protection != content_protection_elements->end(); ++protection) { - if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { - continue; - } - - for (std::vector::iterator subelement = - protection->subelements.begin(); - subelement != protection->subelements.end(); ++subelement) { - if (subelement->name == kPsshElementName) { - // For now, we want to remove the PSSH element because some players do - // not support updating pssh. - protection->subelements.erase(subelement); - - // TODO(rkuroiwa): Uncomment this and remove the line above when - // shaka-player supports updating PSSH. - // subelement->content = pssh; - return; - } - } - - // Reaching here means does not exist under the - // ContentProtection element. Add it. - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // protection->subelements.push_back(cenc_pssh); - return; - } - - // Reaching here means that ContentProtection for the DRM does not exist. - // Add it. - ContentProtectionElement content_protection; - content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // content_protection.subelements.push_back(cenc_pssh); - content_protection_elements->push_back(content_protection); - return; -} - -namespace { - -// UUID for Marlin Adaptive Streaming Specification – Simple Profile from -// https://dashif.org/identifiers/content_protection/. -const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; -// Unofficial FairPlay system id extracted from -// https://forums.developer.apple.com/thread/6185. -const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; -// String representation of media::kPlayReadySystemId. -const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; -// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". -// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. -const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; - -Element GenerateMarlinContentIds(const std::string& key_id) { - // See https://github.com/shaka-project/shaka-packager/issues/381 for details. - static const char kMarlinContentIdName[] = "mas:MarlinContentId"; - static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; - static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; - - Element marlin_content_id; - marlin_content_id.name = kMarlinContentIdName; - marlin_content_id.content = - kMarlinContentIdPrefix + - base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); - - Element marlin_content_ids; - marlin_content_ids.name = kMarlinContentIdsName; - marlin_content_ids.subelements.push_back(marlin_content_id); - - return marlin_content_ids; -} - -Element GenerateCencPsshElement(const std::string& pssh) { - std::string base64_encoded_pssh; - base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), - &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = base64_encoded_pssh; - return cenc_pssh; -} - -// Extract MS PlayReady Object from given PSSH -// and encode it in base64. -Element GenerateMsprProElement(const std::string& pssh) { - std::unique_ptr b = - media::PsshBoxBuilder::ParseFromBox( - reinterpret_cast(pssh.data()), - pssh.size() - ); - - const std::vector *p_pssh = &b->pssh_data(); - std::string base64_encoded_mspr; - base::Base64Encode( - base::StringPiece( - reinterpret_cast(p_pssh->data()), - p_pssh->size()), - &base64_encoded_mspr - ); - Element mspr_pro; - mspr_pro.name = kMsproElementName; - mspr_pro.content = base64_encoded_mspr; - return mspr_pro; -} - -// Helper function. This works because Representation and AdaptationSet both -// have AddContentProtectionElement(). -template -void AddContentProtectionElementsHelperTemplated( - const MediaInfo& media_info, - ContentProtectionParent* parent) { - DCHECK(parent); - if (!media_info.has_protected_content()) - return; - - const MediaInfo::ProtectedContent& protected_content = - media_info.protected_content(); - - // DASH MPD spec specifies a default ContentProtection element for ISO BMFF - // (MP4) files. - const bool is_mp4_container = - media_info.container_type() == MediaInfo::CONTAINER_MP4; - std::string key_id_uuid_format; - if (protected_content.has_default_key_id() && - !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { - if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { - LOG(ERROR) << "Failed to convert default key ID into UUID format."; - } - } - - if (is_mp4_container) { - ContentProtectionElement mp4_content_protection; - mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; - mp4_content_protection.value = protected_content.protection_scheme(); - if (!key_id_uuid_format.empty()) { - mp4_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(mp4_content_protection); - } - - for (const auto& entry : protected_content.content_protection_entry()) { - if (!entry.has_uuid()) { - LOG(WARNING) - << "ContentProtectionEntry was specified but no UUID is set for " - << entry.name_version() << ", skipping."; - continue; - } - - ContentProtectionElement drm_content_protection; - - if (entry.has_name_version()) - drm_content_protection.value = entry.name_version(); - - if (entry.uuid() == kFairPlayUUID) { - VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " - "not support DASH signaling."; - continue; - } else if (entry.uuid() == kMarlinUUID) { - // Marlin requires its uuid to be in upper case. See #525 for details. - drm_content_protection.scheme_id_uri = - "urn:uuid:" + base::ToUpperASCII(entry.uuid()); - drm_content_protection.subelements.push_back( - GenerateMarlinContentIds(protected_content.default_key_id())); - } else { - drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); - if (!entry.pssh().empty()) { - drm_content_protection.subelements.push_back( - GenerateCencPsshElement(entry.pssh())); - if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { - drm_content_protection.subelements.push_back( - GenerateMsprProElement(entry.pssh())); - drm_content_protection.value = kContentProtectionValueMSPR20; - } - } - } - - if (!key_id_uuid_format.empty() && !is_mp4_container) { - drm_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(drm_content_protection); - } - - VLOG_IF(1, protected_content.content_protection_entry().size() == 0) - << "The media is encrypted but no content protection specified (can " - "happen with key rotation)."; -} -} // namespace - -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -} // namespace shaka +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/mpd/base/mpd_utils.h" + +#include +#include + +#include "packager/base/base64.h" +#include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/string_util.h" +#include "packager/media/base/language_utils.h" +#include "packager/media/base/protection_system_specific_info.h" +#include "packager/mpd/base/adaptation_set.h" +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/representation.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + +DEFINE_bool( + use_legacy_vp9_codec_string, + false, + "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " + "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " + "in https://github.com/shaka-project/shaka-packager/issues/406, all major " + "browsers and platforms already support the new 'vp09' codec string."); + +namespace shaka { +namespace { + +bool IsKeyRotationDefaultKeyId(const std::string& key_id) { + for (char c : key_id) { + if (c != '\0') + return false; + } + return true; +} + +std::string TextCodecString(const MediaInfo& media_info) { + CHECK(media_info.has_text_info()); + const auto container_type = media_info.container_type(); + + // Codecs are not needed when mimeType is "text/*". Having a codec would be + // redundant. + if (container_type == MediaInfo::CONTAINER_TEXT) { + return ""; + } + + // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override + // the default codec value. + const std::string& codec = media_info.text_info().codec(); + if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { + return "stpp"; + } + + return codec; +} + +} // namespace + +bool HasVODOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_range() || media_info.has_index_range() || + media_info.has_media_file_url(); +} + +bool HasLiveOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_segment_url() || + media_info.has_segment_template_url(); +} + +void RemoveDuplicateAttributes( + ContentProtectionElement* content_protection_element) { + DCHECK(content_protection_element); + typedef std::map AttributesMap; + + AttributesMap& attributes = content_protection_element->additional_attributes; + if (!content_protection_element->value.empty()) + attributes.erase("value"); + + if (!content_protection_element->scheme_id_uri.empty()) + attributes.erase("schemeIdUri"); +} + +std::string GetLanguage(const MediaInfo& media_info) { + std::string lang; + if (media_info.has_audio_info()) { + lang = media_info.audio_info().language(); + } else if (media_info.has_text_info()) { + lang = media_info.text_info().language(); + } + return LanguageToShortestForm(lang); +} + +std::string GetCodecs(const MediaInfo& media_info) { + CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), + media_info.has_text_info())); + + if (media_info.has_video_info()) { + if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { + std::string codec = media_info.video_info().codec().substr(0, 4); + // media_info.video_info().codec() contains new revised codec string + // specified by "VPx in ISO BMFF" document, which is not compatible to + // old codec strings in WebM. Hack it here before all browsers support + // new codec strings. + if (codec == "vp08") + return "vp8"; + if (FLAGS_use_legacy_vp9_codec_string) { + if (codec == "vp09") + return "vp9"; + } + } + return media_info.video_info().codec(); + } + + if (media_info.has_audio_info()) + return media_info.audio_info().codec(); + + if (media_info.has_text_info()) + return TextCodecString(media_info); + + NOTREACHED(); + return ""; +} + +std::string GetBaseCodec(const MediaInfo& media_info) { + std::string codec; + if (media_info.has_video_info()) { + codec = media_info.video_info().codec(); + } else if (media_info.has_audio_info()) { + codec = media_info.audio_info().codec(); + } else if (media_info.has_text_info()) { + codec = media_info.text_info().codec(); + } + // Convert, for example, "mp4a.40.2" to simply "mp4a". + // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. + size_t dot = codec.find('.'); + if (dot != std::string::npos) { + codec.erase(dot); + } + return codec; +} + +std::string GetAdaptationSetKey(const MediaInfo& media_info, + bool ignore_codec) { + std::string key; + + if (media_info.has_video_info()) { + key.append("video:"); + } else if (media_info.has_audio_info()) { + key.append("audio:"); + } else if (media_info.has_text_info()) { + key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); + key.append(":"); + } else { + key.append("unknown:"); + } + + key.append(MediaInfo_ContainerType_Name(media_info.container_type())); + if (!ignore_codec) { + key.append(":"); + key.append(GetBaseCodec(media_info)); + + if (media_info.video_info().has_transfer_characteristics()) { + key.append(":"); + key.append( + std::to_string(media_info.video_info().transfer_characteristics())); + } else if (GetBaseCodec(media_info).find("dvh") == 0) { + // Dolby Vision (dvh1 or dvhe) is always HDR. + key.append(":"); + key.append(std::to_string(kTransferFunctionPQ)); + } + } + key.append(":"); + key.append(GetLanguage(media_info)); + + // Trick play streams of the same original stream, but possibly with + // different trick_play_factors, belong to the same trick play AdaptationSet. + if (media_info.video_info().has_playback_rate()) { + key.append(":trick_play"); + } + + if (!media_info.dash_accessibilities().empty()) { + key.append(":accessibility_"); + for (const std::string& accessibility : media_info.dash_accessibilities()) + key.append(accessibility); + } + + if (!media_info.dash_roles().empty()) { + key.append(":roles_"); + for (const std::string& role : media_info.dash_roles()) + key.append(role); + } + + return key; +} + +std::string SecondsToXmlDuration(double seconds) { + // Chrome internally uses time accurate to microseconds, which is implemented + // per MSE spec (https://www.w3.org/TR/media-source/). + // We need a string formatter that has at least microseconds accuracy for a + // normal video (with duration up to 3 hours). Chrome's DoubleToString + // implementation meets the requirement. + return "PT" + base::DoubleToString(seconds) + "S"; +} + +bool GetDurationAttribute(xmlNodePtr node, float* duration) { + DCHECK(node); + DCHECK(duration); + static const char kDuration[] = "duration"; + xml::scoped_xml_ptr duration_value( + xmlGetProp(node, BAD_CAST kDuration)); + + if (!duration_value) + return false; + + double duration_double_precision = 0.0; + if (!base::StringToDouble(reinterpret_cast(duration_value.get()), + &duration_double_precision)) { + return false; + } + + *duration = static_cast(duration_double_precision); + return true; +} + +bool MoreThanOneTrue(bool b1, bool b2, bool b3) { + return (b1 && b2) || (b2 && b3) || (b3 && b1); +} + +bool AtLeastOneTrue(bool b1, bool b2, bool b3) { + return b1 || b2 || b3; +} + +bool OnlyOneTrue(bool b1, bool b2, bool b3) { + return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); +} + +// Coverts binary data into human readable UUID format. +bool HexToUUID(const std::string& data, std::string* uuid_format) { + DCHECK(uuid_format); + const size_t kExpectedUUIDSize = 16; + if (data.size() != kExpectedUUIDSize) { + LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize + << " but is " << data.size() << " and the data in hex is " + << base::HexEncode(data.data(), data.size()); + return false; + } + + const std::string hex_encoded = + base::ToLowerASCII(base::HexEncode(data.data(), data.size())); + DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); + base::StringPiece all(hex_encoded); + // Note UUID has 5 parts separated with dashes. + // e.g. 123e4567-e89b-12d3-a456-426655440000 + // These StringPieces have each part. + base::StringPiece first = all.substr(0, 8); + base::StringPiece second = all.substr(8, 4); + base::StringPiece third = all.substr(12, 4); + base::StringPiece fourth = all.substr(16, 4); + base::StringPiece fifth = all.substr(20, 12); + + // 32 hexadecimal characters with 4 hyphens. + const size_t kHumanReadableUUIDSize = 36; + uuid_format->reserve(kHumanReadableUUIDSize); + first.CopyToString(uuid_format); + uuid_format->append("-"); + second.AppendToString(uuid_format); + uuid_format->append("-"); + third.AppendToString(uuid_format); + uuid_format->append("-"); + fourth.AppendToString(uuid_format); + uuid_format->append("-"); + fifth.AppendToString(uuid_format); + return true; +} + +void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, + const std::string& pssh, + std::list* content_protection_elements) { + const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; + for (std::list::iterator protection = + content_protection_elements->begin(); + protection != content_protection_elements->end(); ++protection) { + if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { + continue; + } + + for (std::vector::iterator subelement = + protection->subelements.begin(); + subelement != protection->subelements.end(); ++subelement) { + if (subelement->name == kPsshElementName) { + // For now, we want to remove the PSSH element because some players do + // not support updating pssh. + protection->subelements.erase(subelement); + + // TODO(rkuroiwa): Uncomment this and remove the line above when + // shaka-player supports updating PSSH. + // subelement->content = pssh; + return; + } + } + + // Reaching here means does not exist under the + // ContentProtection element. Add it. + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // protection->subelements.push_back(cenc_pssh); + return; + } + + // Reaching here means that ContentProtection for the DRM does not exist. + // Add it. + ContentProtectionElement content_protection; + content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // content_protection.subelements.push_back(cenc_pssh); + content_protection_elements->push_back(content_protection); + return; +} + +namespace { + +// UUID for Marlin Adaptive Streaming Specification – Simple Profile from +// https://dashif.org/identifiers/content_protection/. +const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; +// Unofficial FairPlay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; +// String representation of media::kPlayReadySystemId. +const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; +// It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". +// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; + +Element GenerateMarlinContentIds(const std::string& key_id) { + // See https://github.com/shaka-project/shaka-packager/issues/381 for details. + static const char kMarlinContentIdName[] = "mas:MarlinContentId"; + static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; + static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; + + Element marlin_content_id; + marlin_content_id.name = kMarlinContentIdName; + marlin_content_id.content = + kMarlinContentIdPrefix + + base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); + + Element marlin_content_ids; + marlin_content_ids.name = kMarlinContentIdsName; + marlin_content_ids.subelements.push_back(marlin_content_id); + + return marlin_content_ids; +} + +Element GenerateCencPsshElement(const std::string& pssh) { + std::string base64_encoded_pssh; + base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), + &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + return cenc_pssh; +} + +// Extract MS PlayReady Object from given PSSH +// and encode it in base64. +Element GenerateMsprProElement(const std::string& pssh) { + std::unique_ptr b = + media::PsshBoxBuilder::ParseFromBox( + reinterpret_cast(pssh.data()), + pssh.size() + ); + + const std::vector *p_pssh = &b->pssh_data(); + std::string base64_encoded_mspr; + base::Base64Encode( + base::StringPiece( + reinterpret_cast(p_pssh->data()), + p_pssh->size()), + &base64_encoded_mspr + ); + Element mspr_pro; + mspr_pro.name = kMsproElementName; + mspr_pro.content = base64_encoded_mspr; + return mspr_pro; +} + +// Helper function. This works because Representation and AdaptationSet both +// have AddContentProtectionElement(). +template +void AddContentProtectionElementsHelperTemplated( + const MediaInfo& media_info, + ContentProtectionParent* parent) { + DCHECK(parent); + if (!media_info.has_protected_content()) + return; + + const MediaInfo::ProtectedContent& protected_content = + media_info.protected_content(); + + // DASH MPD spec specifies a default ContentProtection element for ISO BMFF + // (MP4) files. + const bool is_mp4_container = + media_info.container_type() == MediaInfo::CONTAINER_MP4; + std::string key_id_uuid_format; + if (protected_content.has_default_key_id() && + !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { + if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { + LOG(ERROR) << "Failed to convert default key ID into UUID format."; + } + } + + if (is_mp4_container) { + ContentProtectionElement mp4_content_protection; + mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; + mp4_content_protection.value = protected_content.protection_scheme(); + if (!key_id_uuid_format.empty()) { + mp4_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(mp4_content_protection); + } + + for (const auto& entry : protected_content.content_protection_entry()) { + if (!entry.has_uuid()) { + LOG(WARNING) + << "ContentProtectionEntry was specified but no UUID is set for " + << entry.name_version() << ", skipping."; + continue; + } + + ContentProtectionElement drm_content_protection; + + if (entry.has_name_version()) + drm_content_protection.value = entry.name_version(); + + if (entry.uuid() == kFairPlayUUID) { + VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " + "not support DASH signaling."; + continue; + } else if (entry.uuid() == kMarlinUUID) { + // Marlin requires its uuid to be in upper case. See #525 for details. + drm_content_protection.scheme_id_uri = + "urn:uuid:" + base::ToUpperASCII(entry.uuid()); + drm_content_protection.subelements.push_back( + GenerateMarlinContentIds(protected_content.default_key_id())); + } else { + drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (!entry.pssh().empty()) { + drm_content_protection.subelements.push_back( + GenerateCencPsshElement(entry.pssh())); + if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { + drm_content_protection.subelements.push_back( + GenerateMsprProElement(entry.pssh())); + drm_content_protection.value = kContentProtectionValueMSPR20; + } + } + } + + if (!key_id_uuid_format.empty() && !is_mp4_container) { + drm_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(drm_content_protection); + } + + VLOG_IF(1, protected_content.content_protection_entry().size() == 0) + << "The media is encrypted but no content protection specified (can " + "happen with key rotation)."; +} +} // namespace + +void AddContentProtectionElements(const MediaInfo& media_info, + Representation* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +void AddContentProtectionElements(const MediaInfo& media_info, + AdaptationSet* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +} // namespace shaka From da478b681a462064f879badc485759d105c39570 Mon Sep 17 00:00:00 2001 From: sr90 Date: Fri, 1 Sep 2023 12:05:51 -0700 Subject: [PATCH 10/11] Revert "Add comment and flip order for Dolby special case" This reverts commit c5a6009c03188e393e6ada0f5bb61da10b57f27e. --- packager/mpd/base/mpd_utils.cc | 983 ++++++++++++++++----------------- 1 file changed, 491 insertions(+), 492 deletions(-) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index df5ec4c24a8..4840f2020d8 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -1,493 +1,492 @@ -// Copyright 2014 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -#include "packager/mpd/base/mpd_utils.h" - -#include -#include - -#include "packager/base/base64.h" -#include "packager/base/logging.h" -#include "packager/base/strings/string_number_conversions.h" -#include "packager/base/strings/string_util.h" -#include "packager/media/base/language_utils.h" -#include "packager/media/base/protection_system_specific_info.h" -#include "packager/mpd/base/adaptation_set.h" -#include "packager/mpd/base/content_protection_element.h" -#include "packager/mpd/base/representation.h" -#include "packager/mpd/base/xml/scoped_xml_ptr.h" - -DEFINE_bool( - use_legacy_vp9_codec_string, - false, - "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " - "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " - "in https://github.com/shaka-project/shaka-packager/issues/406, all major " - "browsers and platforms already support the new 'vp09' codec string."); - -namespace shaka { -namespace { - -bool IsKeyRotationDefaultKeyId(const std::string& key_id) { - for (char c : key_id) { - if (c != '\0') - return false; - } - return true; -} - -std::string TextCodecString(const MediaInfo& media_info) { - CHECK(media_info.has_text_info()); - const auto container_type = media_info.container_type(); - - // Codecs are not needed when mimeType is "text/*". Having a codec would be - // redundant. - if (container_type == MediaInfo::CONTAINER_TEXT) { - return ""; - } - - // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override - // the default codec value. - const std::string& codec = media_info.text_info().codec(); - if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { - return "stpp"; - } - - return codec; -} - -} // namespace - -bool HasVODOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_range() || media_info.has_index_range() || - media_info.has_media_file_url(); -} - -bool HasLiveOnlyFields(const MediaInfo& media_info) { - return media_info.has_init_segment_url() || - media_info.has_segment_template_url(); -} - -void RemoveDuplicateAttributes( - ContentProtectionElement* content_protection_element) { - DCHECK(content_protection_element); - typedef std::map AttributesMap; - - AttributesMap& attributes = content_protection_element->additional_attributes; - if (!content_protection_element->value.empty()) - attributes.erase("value"); - - if (!content_protection_element->scheme_id_uri.empty()) - attributes.erase("schemeIdUri"); -} - -std::string GetLanguage(const MediaInfo& media_info) { - std::string lang; - if (media_info.has_audio_info()) { - lang = media_info.audio_info().language(); - } else if (media_info.has_text_info()) { - lang = media_info.text_info().language(); - } - return LanguageToShortestForm(lang); -} - -std::string GetCodecs(const MediaInfo& media_info) { - CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), - media_info.has_text_info())); - - if (media_info.has_video_info()) { - if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { - std::string codec = media_info.video_info().codec().substr(0, 4); - // media_info.video_info().codec() contains new revised codec string - // specified by "VPx in ISO BMFF" document, which is not compatible to - // old codec strings in WebM. Hack it here before all browsers support - // new codec strings. - if (codec == "vp08") - return "vp8"; - if (FLAGS_use_legacy_vp9_codec_string) { - if (codec == "vp09") - return "vp9"; - } - } - return media_info.video_info().codec(); - } - - if (media_info.has_audio_info()) - return media_info.audio_info().codec(); - - if (media_info.has_text_info()) - return TextCodecString(media_info); - - NOTREACHED(); - return ""; -} - -std::string GetBaseCodec(const MediaInfo& media_info) { - std::string codec; - if (media_info.has_video_info()) { - codec = media_info.video_info().codec(); - } else if (media_info.has_audio_info()) { - codec = media_info.audio_info().codec(); - } else if (media_info.has_text_info()) { - codec = media_info.text_info().codec(); - } - // Convert, for example, "mp4a.40.2" to simply "mp4a". - // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. - size_t dot = codec.find('.'); - if (dot != std::string::npos) { - codec.erase(dot); - } - return codec; -} - -std::string GetAdaptationSetKey(const MediaInfo& media_info, - bool ignore_codec) { - std::string key; - - if (media_info.has_video_info()) { - key.append("video:"); - } else if (media_info.has_audio_info()) { - key.append("audio:"); - } else if (media_info.has_text_info()) { - key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); - key.append(":"); - } else { - key.append("unknown:"); - } - - key.append(MediaInfo_ContainerType_Name(media_info.container_type())); - if (!ignore_codec) { - key.append(":"); - key.append(GetBaseCodec(media_info)); - - if (media_info.video_info().has_transfer_characteristics()) { - key.append(":"); - key.append( - std::to_string(media_info.video_info().transfer_characteristics())); - } else if (GetBaseCodec(media_info).find("dvh") == 0) { - // Dolby Vision (dvh1 or dvhe) is always HDR. - key.append(":"); - key.append(std::to_string(kTransferFunctionPQ)); - } - } - key.append(":"); - key.append(GetLanguage(media_info)); - - // Trick play streams of the same original stream, but possibly with - // different trick_play_factors, belong to the same trick play AdaptationSet. - if (media_info.video_info().has_playback_rate()) { - key.append(":trick_play"); - } - - if (!media_info.dash_accessibilities().empty()) { - key.append(":accessibility_"); - for (const std::string& accessibility : media_info.dash_accessibilities()) - key.append(accessibility); - } - - if (!media_info.dash_roles().empty()) { - key.append(":roles_"); - for (const std::string& role : media_info.dash_roles()) - key.append(role); - } - - return key; -} - -std::string SecondsToXmlDuration(double seconds) { - // Chrome internally uses time accurate to microseconds, which is implemented - // per MSE spec (https://www.w3.org/TR/media-source/). - // We need a string formatter that has at least microseconds accuracy for a - // normal video (with duration up to 3 hours). Chrome's DoubleToString - // implementation meets the requirement. - return "PT" + base::DoubleToString(seconds) + "S"; -} - -bool GetDurationAttribute(xmlNodePtr node, float* duration) { - DCHECK(node); - DCHECK(duration); - static const char kDuration[] = "duration"; - xml::scoped_xml_ptr duration_value( - xmlGetProp(node, BAD_CAST kDuration)); - - if (!duration_value) - return false; - - double duration_double_precision = 0.0; - if (!base::StringToDouble(reinterpret_cast(duration_value.get()), - &duration_double_precision)) { - return false; - } - - *duration = static_cast(duration_double_precision); - return true; -} - -bool MoreThanOneTrue(bool b1, bool b2, bool b3) { - return (b1 && b2) || (b2 && b3) || (b3 && b1); -} - -bool AtLeastOneTrue(bool b1, bool b2, bool b3) { - return b1 || b2 || b3; -} - -bool OnlyOneTrue(bool b1, bool b2, bool b3) { - return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); -} - -// Coverts binary data into human readable UUID format. -bool HexToUUID(const std::string& data, std::string* uuid_format) { - DCHECK(uuid_format); - const size_t kExpectedUUIDSize = 16; - if (data.size() != kExpectedUUIDSize) { - LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize - << " but is " << data.size() << " and the data in hex is " - << base::HexEncode(data.data(), data.size()); - return false; - } - - const std::string hex_encoded = - base::ToLowerASCII(base::HexEncode(data.data(), data.size())); - DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); - base::StringPiece all(hex_encoded); - // Note UUID has 5 parts separated with dashes. - // e.g. 123e4567-e89b-12d3-a456-426655440000 - // These StringPieces have each part. - base::StringPiece first = all.substr(0, 8); - base::StringPiece second = all.substr(8, 4); - base::StringPiece third = all.substr(12, 4); - base::StringPiece fourth = all.substr(16, 4); - base::StringPiece fifth = all.substr(20, 12); - - // 32 hexadecimal characters with 4 hyphens. - const size_t kHumanReadableUUIDSize = 36; - uuid_format->reserve(kHumanReadableUUIDSize); - first.CopyToString(uuid_format); - uuid_format->append("-"); - second.AppendToString(uuid_format); - uuid_format->append("-"); - third.AppendToString(uuid_format); - uuid_format->append("-"); - fourth.AppendToString(uuid_format); - uuid_format->append("-"); - fifth.AppendToString(uuid_format); - return true; -} - -void UpdateContentProtectionPsshHelper( - const std::string& drm_uuid, - const std::string& pssh, - std::list* content_protection_elements) { - const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; - for (std::list::iterator protection = - content_protection_elements->begin(); - protection != content_protection_elements->end(); ++protection) { - if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { - continue; - } - - for (std::vector::iterator subelement = - protection->subelements.begin(); - subelement != protection->subelements.end(); ++subelement) { - if (subelement->name == kPsshElementName) { - // For now, we want to remove the PSSH element because some players do - // not support updating pssh. - protection->subelements.erase(subelement); - - // TODO(rkuroiwa): Uncomment this and remove the line above when - // shaka-player supports updating PSSH. - // subelement->content = pssh; - return; - } - } - - // Reaching here means does not exist under the - // ContentProtection element. Add it. - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // protection->subelements.push_back(cenc_pssh); - return; - } - - // Reaching here means that ContentProtection for the DRM does not exist. - // Add it. - ContentProtectionElement content_protection; - content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; - // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. - // Element cenc_pssh; - // cenc_pssh.name = kPsshElementName; - // cenc_pssh.content = pssh; - // content_protection.subelements.push_back(cenc_pssh); - content_protection_elements->push_back(content_protection); - return; -} - -namespace { - -// UUID for Marlin Adaptive Streaming Specification – Simple Profile from -// https://dashif.org/identifiers/content_protection/. -const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; -// Unofficial FairPlay system id extracted from -// https://forums.developer.apple.com/thread/6185. -const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; -// String representation of media::kPlayReadySystemId. -const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/mpd/base/mpd_utils.h" + +#include +#include + +#include "packager/base/base64.h" +#include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/string_util.h" +#include "packager/media/base/language_utils.h" +#include "packager/media/base/protection_system_specific_info.h" +#include "packager/mpd/base/adaptation_set.h" +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/representation.h" +#include "packager/mpd/base/xml/scoped_xml_ptr.h" + +DEFINE_bool( + use_legacy_vp9_codec_string, + false, + "Use legacy vp9 codec string 'vp9' if set to true; otherwise new style " + "vp09.xx.xx.xx... codec string will be used. Default to false as indicated " + "in https://github.com/shaka-project/shaka-packager/issues/406, all major " + "browsers and platforms already support the new 'vp09' codec string."); + +namespace shaka { +namespace { + +bool IsKeyRotationDefaultKeyId(const std::string& key_id) { + for (char c : key_id) { + if (c != '\0') + return false; + } + return true; +} + +std::string TextCodecString(const MediaInfo& media_info) { + CHECK(media_info.has_text_info()); + const auto container_type = media_info.container_type(); + + // Codecs are not needed when mimeType is "text/*". Having a codec would be + // redundant. + if (container_type == MediaInfo::CONTAINER_TEXT) { + return ""; + } + + // DASH IOP mentions that the codec for ttml in mp4 is stpp, so override + // the default codec value. + const std::string& codec = media_info.text_info().codec(); + if (codec == "ttml" && container_type == MediaInfo::CONTAINER_MP4) { + return "stpp"; + } + + return codec; +} + +} // namespace + +bool HasVODOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_range() || media_info.has_index_range() || + media_info.has_media_file_url(); +} + +bool HasLiveOnlyFields(const MediaInfo& media_info) { + return media_info.has_init_segment_url() || + media_info.has_segment_template_url(); +} + +void RemoveDuplicateAttributes( + ContentProtectionElement* content_protection_element) { + DCHECK(content_protection_element); + typedef std::map AttributesMap; + + AttributesMap& attributes = content_protection_element->additional_attributes; + if (!content_protection_element->value.empty()) + attributes.erase("value"); + + if (!content_protection_element->scheme_id_uri.empty()) + attributes.erase("schemeIdUri"); +} + +std::string GetLanguage(const MediaInfo& media_info) { + std::string lang; + if (media_info.has_audio_info()) { + lang = media_info.audio_info().language(); + } else if (media_info.has_text_info()) { + lang = media_info.text_info().language(); + } + return LanguageToShortestForm(lang); +} + +std::string GetCodecs(const MediaInfo& media_info) { + CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(), + media_info.has_text_info())); + + if (media_info.has_video_info()) { + if (media_info.container_type() == MediaInfo::CONTAINER_WEBM) { + std::string codec = media_info.video_info().codec().substr(0, 4); + // media_info.video_info().codec() contains new revised codec string + // specified by "VPx in ISO BMFF" document, which is not compatible to + // old codec strings in WebM. Hack it here before all browsers support + // new codec strings. + if (codec == "vp08") + return "vp8"; + if (FLAGS_use_legacy_vp9_codec_string) { + if (codec == "vp09") + return "vp9"; + } + } + return media_info.video_info().codec(); + } + + if (media_info.has_audio_info()) + return media_info.audio_info().codec(); + + if (media_info.has_text_info()) + return TextCodecString(media_info); + + NOTREACHED(); + return ""; +} + +std::string GetBaseCodec(const MediaInfo& media_info) { + std::string codec; + if (media_info.has_video_info()) { + codec = media_info.video_info().codec(); + } else if (media_info.has_audio_info()) { + codec = media_info.audio_info().codec(); + } else if (media_info.has_text_info()) { + codec = media_info.text_info().codec(); + } + // Convert, for example, "mp4a.40.2" to simply "mp4a". + // "mp4a.40.2" and "mp4a.40.5" can exist in the same AdaptationSet. + size_t dot = codec.find('.'); + if (dot != std::string::npos) { + codec.erase(dot); + } + return codec; +} + +std::string GetAdaptationSetKey(const MediaInfo& media_info, + bool ignore_codec) { + std::string key; + + if (media_info.has_video_info()) { + key.append("video:"); + } else if (media_info.has_audio_info()) { + key.append("audio:"); + } else if (media_info.has_text_info()) { + key.append(MediaInfo_TextInfo_TextType_Name(media_info.text_info().type())); + key.append(":"); + } else { + key.append("unknown:"); + } + + key.append(MediaInfo_ContainerType_Name(media_info.container_type())); + if (!ignore_codec) { + key.append(":"); + key.append(GetBaseCodec(media_info)); + + if (GetBaseCodec(media_info).find("dvh") == 0) { + key.append(":"); + key.append(std::to_string(kTransferFunctionPQ)); + } else if (media_info.video_info().has_transfer_characteristics()) { + key.append(":"); + key.append( + std::to_string(media_info.video_info().transfer_characteristics())); + } + } + key.append(":"); + key.append(GetLanguage(media_info)); + + // Trick play streams of the same original stream, but possibly with + // different trick_play_factors, belong to the same trick play AdaptationSet. + if (media_info.video_info().has_playback_rate()) { + key.append(":trick_play"); + } + + if (!media_info.dash_accessibilities().empty()) { + key.append(":accessibility_"); + for (const std::string& accessibility : media_info.dash_accessibilities()) + key.append(accessibility); + } + + if (!media_info.dash_roles().empty()) { + key.append(":roles_"); + for (const std::string& role : media_info.dash_roles()) + key.append(role); + } + + return key; +} + +std::string SecondsToXmlDuration(double seconds) { + // Chrome internally uses time accurate to microseconds, which is implemented + // per MSE spec (https://www.w3.org/TR/media-source/). + // We need a string formatter that has at least microseconds accuracy for a + // normal video (with duration up to 3 hours). Chrome's DoubleToString + // implementation meets the requirement. + return "PT" + base::DoubleToString(seconds) + "S"; +} + +bool GetDurationAttribute(xmlNodePtr node, float* duration) { + DCHECK(node); + DCHECK(duration); + static const char kDuration[] = "duration"; + xml::scoped_xml_ptr duration_value( + xmlGetProp(node, BAD_CAST kDuration)); + + if (!duration_value) + return false; + + double duration_double_precision = 0.0; + if (!base::StringToDouble(reinterpret_cast(duration_value.get()), + &duration_double_precision)) { + return false; + } + + *duration = static_cast(duration_double_precision); + return true; +} + +bool MoreThanOneTrue(bool b1, bool b2, bool b3) { + return (b1 && b2) || (b2 && b3) || (b3 && b1); +} + +bool AtLeastOneTrue(bool b1, bool b2, bool b3) { + return b1 || b2 || b3; +} + +bool OnlyOneTrue(bool b1, bool b2, bool b3) { + return !MoreThanOneTrue(b1, b2, b3) && AtLeastOneTrue(b1, b2, b3); +} + +// Coverts binary data into human readable UUID format. +bool HexToUUID(const std::string& data, std::string* uuid_format) { + DCHECK(uuid_format); + const size_t kExpectedUUIDSize = 16; + if (data.size() != kExpectedUUIDSize) { + LOG(ERROR) << "UUID size is expected to be " << kExpectedUUIDSize + << " but is " << data.size() << " and the data in hex is " + << base::HexEncode(data.data(), data.size()); + return false; + } + + const std::string hex_encoded = + base::ToLowerASCII(base::HexEncode(data.data(), data.size())); + DCHECK_EQ(hex_encoded.size(), kExpectedUUIDSize * 2); + base::StringPiece all(hex_encoded); + // Note UUID has 5 parts separated with dashes. + // e.g. 123e4567-e89b-12d3-a456-426655440000 + // These StringPieces have each part. + base::StringPiece first = all.substr(0, 8); + base::StringPiece second = all.substr(8, 4); + base::StringPiece third = all.substr(12, 4); + base::StringPiece fourth = all.substr(16, 4); + base::StringPiece fifth = all.substr(20, 12); + + // 32 hexadecimal characters with 4 hyphens. + const size_t kHumanReadableUUIDSize = 36; + uuid_format->reserve(kHumanReadableUUIDSize); + first.CopyToString(uuid_format); + uuid_format->append("-"); + second.AppendToString(uuid_format); + uuid_format->append("-"); + third.AppendToString(uuid_format); + uuid_format->append("-"); + fourth.AppendToString(uuid_format); + uuid_format->append("-"); + fifth.AppendToString(uuid_format); + return true; +} + +void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, + const std::string& pssh, + std::list* content_protection_elements) { + const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; + for (std::list::iterator protection = + content_protection_elements->begin(); + protection != content_protection_elements->end(); ++protection) { + if (protection->scheme_id_uri != drm_uuid_schemd_id_uri_form) { + continue; + } + + for (std::vector::iterator subelement = + protection->subelements.begin(); + subelement != protection->subelements.end(); ++subelement) { + if (subelement->name == kPsshElementName) { + // For now, we want to remove the PSSH element because some players do + // not support updating pssh. + protection->subelements.erase(subelement); + + // TODO(rkuroiwa): Uncomment this and remove the line above when + // shaka-player supports updating PSSH. + // subelement->content = pssh; + return; + } + } + + // Reaching here means does not exist under the + // ContentProtection element. Add it. + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // protection->subelements.push_back(cenc_pssh); + return; + } + + // Reaching here means that ContentProtection for the DRM does not exist. + // Add it. + ContentProtectionElement content_protection; + content_protection.scheme_id_uri = drm_uuid_schemd_id_uri_form; + // TODO(rkuroiwa): Uncomment this when shaka-player supports updating PSSH. + // Element cenc_pssh; + // cenc_pssh.name = kPsshElementName; + // cenc_pssh.content = pssh; + // content_protection.subelements.push_back(cenc_pssh); + content_protection_elements->push_back(content_protection); + return; +} + +namespace { + +// UUID for Marlin Adaptive Streaming Specification – Simple Profile from +// https://dashif.org/identifiers/content_protection/. +const char kMarlinUUID[] = "5e629af5-38da-4063-8977-97ffbd9902d4"; +// Unofficial FairPlay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const char kFairPlayUUID[] = "29701fe4-3cc7-4a34-8c5b-ae90c7439a47"; +// String representation of media::kPlayReadySystemId. +const char kPlayReadyUUID[] = "9a04f079-9840-4286-ab92-e65be0885f95"; // It is RECOMMENDED to include the @value attribute with name and version "MSPR 2.0". -// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. -const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; - -Element GenerateMarlinContentIds(const std::string& key_id) { - // See https://github.com/shaka-project/shaka-packager/issues/381 for details. - static const char kMarlinContentIdName[] = "mas:MarlinContentId"; - static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; - static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; - - Element marlin_content_id; - marlin_content_id.name = kMarlinContentIdName; - marlin_content_id.content = - kMarlinContentIdPrefix + - base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); - - Element marlin_content_ids; - marlin_content_ids.name = kMarlinContentIdsName; - marlin_content_ids.subelements.push_back(marlin_content_id); - - return marlin_content_ids; -} - -Element GenerateCencPsshElement(const std::string& pssh) { - std::string base64_encoded_pssh; - base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), - &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = base64_encoded_pssh; - return cenc_pssh; -} - -// Extract MS PlayReady Object from given PSSH -// and encode it in base64. -Element GenerateMsprProElement(const std::string& pssh) { - std::unique_ptr b = - media::PsshBoxBuilder::ParseFromBox( - reinterpret_cast(pssh.data()), - pssh.size() - ); - - const std::vector *p_pssh = &b->pssh_data(); - std::string base64_encoded_mspr; - base::Base64Encode( - base::StringPiece( - reinterpret_cast(p_pssh->data()), - p_pssh->size()), - &base64_encoded_mspr - ); - Element mspr_pro; - mspr_pro.name = kMsproElementName; - mspr_pro.content = base64_encoded_mspr; - return mspr_pro; -} - -// Helper function. This works because Representation and AdaptationSet both -// have AddContentProtectionElement(). -template -void AddContentProtectionElementsHelperTemplated( - const MediaInfo& media_info, - ContentProtectionParent* parent) { - DCHECK(parent); - if (!media_info.has_protected_content()) - return; - - const MediaInfo::ProtectedContent& protected_content = - media_info.protected_content(); - - // DASH MPD spec specifies a default ContentProtection element for ISO BMFF - // (MP4) files. - const bool is_mp4_container = - media_info.container_type() == MediaInfo::CONTAINER_MP4; - std::string key_id_uuid_format; - if (protected_content.has_default_key_id() && - !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { - if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { - LOG(ERROR) << "Failed to convert default key ID into UUID format."; - } - } - - if (is_mp4_container) { - ContentProtectionElement mp4_content_protection; - mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; - mp4_content_protection.value = protected_content.protection_scheme(); - if (!key_id_uuid_format.empty()) { - mp4_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(mp4_content_protection); - } - - for (const auto& entry : protected_content.content_protection_entry()) { - if (!entry.has_uuid()) { - LOG(WARNING) - << "ContentProtectionEntry was specified but no UUID is set for " - << entry.name_version() << ", skipping."; - continue; - } - - ContentProtectionElement drm_content_protection; - - if (entry.has_name_version()) - drm_content_protection.value = entry.name_version(); - - if (entry.uuid() == kFairPlayUUID) { - VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " - "not support DASH signaling."; - continue; - } else if (entry.uuid() == kMarlinUUID) { - // Marlin requires its uuid to be in upper case. See #525 for details. - drm_content_protection.scheme_id_uri = - "urn:uuid:" + base::ToUpperASCII(entry.uuid()); - drm_content_protection.subelements.push_back( - GenerateMarlinContentIds(protected_content.default_key_id())); - } else { - drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); - if (!entry.pssh().empty()) { - drm_content_protection.subelements.push_back( - GenerateCencPsshElement(entry.pssh())); - if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { - drm_content_protection.subelements.push_back( - GenerateMsprProElement(entry.pssh())); - drm_content_protection.value = kContentProtectionValueMSPR20; - } - } - } - - if (!key_id_uuid_format.empty() && !is_mp4_container) { - drm_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } - - parent->AddContentProtectionElement(drm_content_protection); - } - - VLOG_IF(1, protected_content.content_protection_entry().size() == 0) - << "The media is encrypted but no content protection specified (can " - "happen with key rotation)."; -} -} // namespace - -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* parent) { - AddContentProtectionElementsHelperTemplated(media_info, parent); -} - -} // namespace shaka +// See https://docs.microsoft.com/en-us/playready/specifications/mpeg-dash-playready#221-general. +const char kContentProtectionValueMSPR20[] = "MSPR 2.0"; + +Element GenerateMarlinContentIds(const std::string& key_id) { + // See https://github.com/shaka-project/shaka-packager/issues/381 for details. + static const char kMarlinContentIdName[] = "mas:MarlinContentId"; + static const char kMarlinContentIdPrefix[] = "urn:marlin:kid:"; + static const char kMarlinContentIdsName[] = "mas:MarlinContentIds"; + + Element marlin_content_id; + marlin_content_id.name = kMarlinContentIdName; + marlin_content_id.content = + kMarlinContentIdPrefix + + base::ToLowerASCII(base::HexEncode(key_id.data(), key_id.size())); + + Element marlin_content_ids; + marlin_content_ids.name = kMarlinContentIdsName; + marlin_content_ids.subelements.push_back(marlin_content_id); + + return marlin_content_ids; +} + +Element GenerateCencPsshElement(const std::string& pssh) { + std::string base64_encoded_pssh; + base::Base64Encode(base::StringPiece(pssh.data(), pssh.size()), + &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + return cenc_pssh; +} + +// Extract MS PlayReady Object from given PSSH +// and encode it in base64. +Element GenerateMsprProElement(const std::string& pssh) { + std::unique_ptr b = + media::PsshBoxBuilder::ParseFromBox( + reinterpret_cast(pssh.data()), + pssh.size() + ); + + const std::vector *p_pssh = &b->pssh_data(); + std::string base64_encoded_mspr; + base::Base64Encode( + base::StringPiece( + reinterpret_cast(p_pssh->data()), + p_pssh->size()), + &base64_encoded_mspr + ); + Element mspr_pro; + mspr_pro.name = kMsproElementName; + mspr_pro.content = base64_encoded_mspr; + return mspr_pro; +} + +// Helper function. This works because Representation and AdaptationSet both +// have AddContentProtectionElement(). +template +void AddContentProtectionElementsHelperTemplated( + const MediaInfo& media_info, + ContentProtectionParent* parent) { + DCHECK(parent); + if (!media_info.has_protected_content()) + return; + + const MediaInfo::ProtectedContent& protected_content = + media_info.protected_content(); + + // DASH MPD spec specifies a default ContentProtection element for ISO BMFF + // (MP4) files. + const bool is_mp4_container = + media_info.container_type() == MediaInfo::CONTAINER_MP4; + std::string key_id_uuid_format; + if (protected_content.has_default_key_id() && + !IsKeyRotationDefaultKeyId(protected_content.default_key_id())) { + if (!HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { + LOG(ERROR) << "Failed to convert default key ID into UUID format."; + } + } + + if (is_mp4_container) { + ContentProtectionElement mp4_content_protection; + mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; + mp4_content_protection.value = protected_content.protection_scheme(); + if (!key_id_uuid_format.empty()) { + mp4_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(mp4_content_protection); + } + + for (const auto& entry : protected_content.content_protection_entry()) { + if (!entry.has_uuid()) { + LOG(WARNING) + << "ContentProtectionEntry was specified but no UUID is set for " + << entry.name_version() << ", skipping."; + continue; + } + + ContentProtectionElement drm_content_protection; + + if (entry.has_name_version()) + drm_content_protection.value = entry.name_version(); + + if (entry.uuid() == kFairPlayUUID) { + VLOG(1) << "Skipping FairPlay ContentProtection element as FairPlay does " + "not support DASH signaling."; + continue; + } else if (entry.uuid() == kMarlinUUID) { + // Marlin requires its uuid to be in upper case. See #525 for details. + drm_content_protection.scheme_id_uri = + "urn:uuid:" + base::ToUpperASCII(entry.uuid()); + drm_content_protection.subelements.push_back( + GenerateMarlinContentIds(protected_content.default_key_id())); + } else { + drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (!entry.pssh().empty()) { + drm_content_protection.subelements.push_back( + GenerateCencPsshElement(entry.pssh())); + if(entry.uuid() == kPlayReadyUUID && protected_content.include_mspr_pro()) { + drm_content_protection.subelements.push_back( + GenerateMsprProElement(entry.pssh())); + drm_content_protection.value = kContentProtectionValueMSPR20; + } + } + } + + if (!key_id_uuid_format.empty() && !is_mp4_container) { + drm_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } + + parent->AddContentProtectionElement(drm_content_protection); + } + + VLOG_IF(1, protected_content.content_protection_entry().size() == 0) + << "The media is encrypted but no content protection specified (can " + "happen with key rotation)."; +} +} // namespace + +void AddContentProtectionElements(const MediaInfo& media_info, + Representation* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +void AddContentProtectionElements(const MediaInfo& media_info, + AdaptationSet* parent) { + AddContentProtectionElementsHelperTemplated(media_info, parent); +} + +} // namespace shaka From 9f44b116f78f0caf9205f26140c7b309521047e8 Mon Sep 17 00:00:00 2001 From: sr90 Date: Sat, 2 Sep 2023 11:59:09 -0700 Subject: [PATCH 11/11] Adding comment. --- packager/mpd/base/mpd_utils.cc | 2 ++ packager/mpd/base/period.cc | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 4840f2020d8..0f945f6538c 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -164,6 +164,8 @@ std::string GetAdaptationSetKey(const MediaInfo& media_info, key.append(GetBaseCodec(media_info)); if (GetBaseCodec(media_info).find("dvh") == 0) { + // Transfer characteristics for Dolby Vision (dvh1 or dvhe) must be PQ + // irrespective of value present in SPS VUI. key.append(":"); key.append(std::to_string(kTransferFunctionPQ)); } else if (media_info.video_info().has_transfer_characteristics()) { diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index a2f2f99d9fe..0cb8273d03d 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -276,6 +276,8 @@ bool Period::SetNewAdaptationSetAttributes( // - Common CCIP values. // Dolby vision: // https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-DASH + // Transfer characteristics for Dolby Vision (dvh1 or dvhe) must be PQ + // irrespective of value present in SPS VUI. if (new_adaptation_set->codec().find("dvh") == 0) { new_adaptation_set->set_transfer_characteristics(kTransferFunctionPQ); } else if (media_info.video_info().has_transfer_characteristics()) {