From 8e113af215d48e704b5b031442fe3841b4e6940c Mon Sep 17 00:00:00 2001 From: Tim Lansen Date: Fri, 17 Apr 2020 20:20:03 +0300 Subject: [PATCH] [DASH] Include alongside to for PlayReady (#749) Configurable under flag --include_mspr_pro_for_playready. True by default. Closes #743. --- AUTHORS | 1 + CONTRIBUTORS | 1 + packager/app/mpd_flags.cc | 5 + packager/app/mpd_flags.h | 1 + packager/app/packager_main.cc | 1 + .../encryption-with-multi-drms/output.mpd | 8 +- .../media/event/mpd_notify_muxer_listener.cc | 1 + .../mpd_notify_muxer_listener_unittest.cc | 3 + packager/mpd/base/media_info.proto | 1 + packager/mpd/base/mpd_builder.cc | 2 + packager/mpd/base/mpd_notifier.h | 3 + packager/mpd/base/mpd_utils.cc | 34 ++++++ packager/mpd/base/mpd_utils.h | 1 + packager/mpd/base/mpd_utils_unittest.cc | 110 ++++++++++++++++++ packager/mpd/public/mpd_params.h | 4 + 15 files changed, 173 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index ca8ecf6851..4f1e15b48c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Anders Hasselqvist Chun-da Chen Daniel CantarĂ­n Google Inc. <*@google.com> +Ivi.ru LLC <*@ivi.ru> Leandro Moreira Leo Law More Screens Ltd. <*@morescreens.net> diff --git a/CONTRIBUTORS b/CONTRIBUTORS index cfa8e208e2..b8d3e079c1 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -40,3 +40,4 @@ Rintaro Kuroiwa Sanil Raut Sergio Ammirata Thomas Inskip +Tim Lansen diff --git a/packager/app/mpd_flags.cc b/packager/app/mpd_flags.cc index 9808ece178..99e3b4e38a 100644 --- a/packager/app/mpd_flags.cc +++ b/packager/app/mpd_flags.cc @@ -64,3 +64,8 @@ DEFINE_bool(allow_codec_switching, "If enabled, allow adaptive switching between different codecs, " "if they have the same language, media type (audio, video etc) and " "container type."); +DEFINE_bool(include_mspr_pro_for_playready, + true, + "If enabled, PlayReady Object will be inserted into " + " element alongside with " + "when using PlayReady protection system."); diff --git a/packager/app/mpd_flags.h b/packager/app/mpd_flags.h index 520739e083..337f4452fb 100644 --- a/packager/app/mpd_flags.h +++ b/packager/app/mpd_flags.h @@ -22,5 +22,6 @@ DECLARE_string(utc_timings); DECLARE_bool(generate_dash_if_iop_compliant_mpd); DECLARE_bool(allow_approximate_segment_timeline); DECLARE_bool(allow_codec_switching); +DECLARE_bool(include_mspr_pro_for_playready); #endif // APP_MPD_FLAGS_H_ diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 35df360b8b..f1d9eb6c0f 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -461,6 +461,7 @@ base::Optional GetPackagingParams() { mpd_params.allow_approximate_segment_timeline = FLAGS_allow_approximate_segment_timeline; mpd_params.allow_codec_switching = FLAGS_allow_codec_switching; + mpd_params.include_mspr_pro = FLAGS_include_mspr_pro_for_playready; HlsParams& hls_params = packaging_params.hls_params; if (!GetHlsPlaylistType(FLAGS_hls_playlist_type, &hls_params.playlist_type)) { diff --git a/packager/app/test/testdata/encryption-with-multi-drms/output.mpd b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd index 954d789ba9..042a364134 100644 --- a/packager/app/test/testdata/encryption-with-multi-drms/output.mpd +++ b/packager/app/test/testdata/encryption-with-multi-drms/output.mpd @@ -1,11 +1,12 @@ - + - + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBOAEQATQB5AE0AVABZADEATwBEAGMANQBNAEQARQB5AE0AegBRADEATgBnAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGwANQBMAG8AVQBnAEsAOQBLAEMAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATgBEAE0AeQBNAFQAWQAxAE8ARABjADUATQBEAEUAeQBNAHoAUQAxAE4AZwA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBsADUATABvAFUAZwBLADkASwBDAGcAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY= @@ -24,8 +25,9 @@ - + AAACJnBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAgYGAgAAAQABAPwBPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBOAEQATQB5AE0AVABZADEATwBEAGMANQBNAEQARQB5AE0AegBRADEATgBnAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGwANQBMAG8AVQBnAEsAOQBLAEMAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA== + BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4ATgBEAE0AeQBNAFQAWQAxAE8ARABjADUATQBEAEUAeQBNAHoAUQAxAE4AZwA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBsADUATABvAFUAZwBLADkASwBDAGcAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEDEyMzQ1Njc4OTAxMjM0NTZI49yVmwY= diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index b4b2f15633..749b7843cb 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -79,6 +79,7 @@ void MpdNotifyMuxerListener::OnMediaStart( if (is_encrypted_) { internal::SetContentProtectionFields(protection_scheme_, default_key_id_, key_system_info_, media_info.get()); + media_info->mutable_protected_content()->set_include_mspr_pro(mpd_notifier_->include_mspr_pro()); } // The content may be splitted into multiple files, but their MediaInfo diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 5323d6a33d..219fac1826 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -168,6 +168,7 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) { " pssh: '" + std::string(kExpectedDefaultPsshBox) + "'\n" " }\n" " default_key_id: 'defaultkeyid'\n" + " include_mspr_pro: 1\n" "}\n"; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); @@ -371,6 +372,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { " pssh: \"" + std::string(kExpectedDefaultPsshBox) + "\"\n" " }\n" " protection_scheme: 'cbcs'\n" + " include_mspr_pro: 1\n" "}\n"; const uint64_t kStartTime1 = 0u; @@ -443,6 +445,7 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { "protected_content {\n" " default_key_id: \"defaultkeyid\"\n" " protection_scheme: 'cbc1'\n" + " include_mspr_pro: 1\n" "}\n"; const uint64_t kStartTime1 = 0u; diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index de1e208ec8..92eaf704ec 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -95,6 +95,7 @@ message MediaInfo { // Specifies the protection scheme: 'cenc', 'cens', 'cbc1', 'cbcs'. // "cbca" is also valid which is a place holder for SAMPLE-AES encryption. optional string protection_scheme = 3 [default = 'cenc']; + optional bool include_mspr_pro = 4 [default = true]; } // TODO(rkuroiwa): Remove this. element that must be added diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 926533d55a..f515586563 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -49,11 +49,13 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) { static const char kMarlinNamespace[] = "urn:marlin:mas:1-0:services:schemas:mpd"; static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink"; + static const char kMsprNamespace[] = "urn:microsoft:playready"; const std::map uris = { {"cenc", kCencNamespace}, {"mas", kMarlinNamespace}, {"xlink", kXmlNamespaceXlink}, + {"mspr", kMsprNamespace}, }; for (const std::string& namespace_name : namespaces) { diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index e9a0cb36d9..6576b7c625 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -104,6 +104,9 @@ class MpdNotifier { /// forces a flush. virtual bool Flush() = 0; + /// @return include_mspr_pro option flag + bool include_mspr_pro() const { return mpd_options_.mpd_params.include_mspr_pro; } + /// @return The dash profile for this object. DashProfile dash_profile() const { return mpd_options_.dash_profile; } diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index d0456f80fb..4feafe72f5 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -14,6 +14,7 @@ #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" @@ -324,6 +325,11 @@ 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/google/shaka-packager/issues/381 for details. @@ -354,6 +360,29 @@ Element GenerateCencPsshElement(const std::string& 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 @@ -419,6 +448,11 @@ void AddContentProtectionElementsHelperTemplated( 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; + } } } diff --git a/packager/mpd/base/mpd_utils.h b/packager/mpd/base/mpd_utils.h index 817b8db331..3bad952fa2 100644 --- a/packager/mpd/base/mpd_utils.h +++ b/packager/mpd/base/mpd_utils.h @@ -24,6 +24,7 @@ struct SegmentInfo; const char kEncryptedMp4Scheme[] = "urn:mpeg:dash:mp4protection:2011"; const char kPsshElementName[] = "cenc:pssh"; +const char kMsproElementName[] = "mspr:pro"; bool HasVODOnlyFields(const MediaInfo& media_info); diff --git a/packager/mpd/base/mpd_utils_unittest.cc b/packager/mpd/base/mpd_utils_unittest.cc index 37b512508d..122d6ee19a 100644 --- a/packager/mpd/base/mpd_utils_unittest.cc +++ b/packager/mpd/base/mpd_utils_unittest.cc @@ -9,6 +9,7 @@ #include #include +#include "packager/base/strings/string_number_conversions.h" #include "packager/mpd/base/adaptation_set.h" #include "packager/mpd/base/mpd_options.h" #include "packager/mpd/test/mpd_builder_test_helper.h" @@ -116,4 +117,113 @@ TEST_F(MpdUtilsTest, ContentProtectionMarlin) { EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); } +TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCencMspr) { + const std::string pssh_str("0000003870737368010000009A04F079" + "98404286AB92E65BE0885F9500000001" + "11223344556677889900AABBCCDDEEFF" + "0000000430313233"); + std::vector pssh; + base::HexStringToBytes(pssh_str, &pssh); + + const char kMediaInfoWithContentProtection[] = + "video_info {" + " codec: 'avc1'" + " width: 1920" + " height: 1080" + " time_scale: 3000" + " frame_duration: 100" + "}" + "protected_content {" + " protection_scheme: 'cenc'" + " default_key_id: '0123456789\x3A\x3B\x3C\x3D\x3E\x3F'" + " include_mspr_pro: 1" + "}" + "container_type: 1"; + + MediaInfo media_info = + ConvertToMediaInfo(kMediaInfoWithContentProtection); + + MediaInfo::ProtectedContent * protected_content = + media_info.mutable_protected_content(); + MediaInfo::ProtectedContent::ContentProtectionEntry* entry = + protected_content->add_content_protection_entry(); + entry->set_uuid("9a04f079-9840-4286-ab92-e65be0885f95"); + entry->set_pssh(pssh.data(), pssh.size()); + + AddContentProtectionElements(media_info, &adaptation_set_); + ASSERT_TRUE(adaptation_set_.AddRepresentation(media_info)); + + const char kExpectedOutput[] = + "" + " " + " " + " " + "AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/AAAABDAxMjM=" + " " + " MDEyMw==" + " " + " " + ""; + + EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + +TEST_F(MpdUtilsTest, ContentProtectionPlayReadyCenc) { + const std::string pssh_str("0000003870737368010000009A04F079" + "98404286AB92E65BE0885F9500000001" + "11223344556677889900AABBCCDDEEFF" + "0000000430313233"); + std::vector pssh; + base::HexStringToBytes(pssh_str, &pssh); + + const char kMediaInfoWithContentProtection[] = + "video_info {" + " codec: 'avc1'" + " width: 1920" + " height: 1080" + " time_scale: 3000" + " frame_duration: 100" + "}" + "protected_content {" + " protection_scheme: 'cenc'" + " default_key_id: '0123456789\x3A\x3B\x3C\x3D\x3E\x3F'" + " include_mspr_pro: 0" + "}" + "container_type: 1"; + + MediaInfo media_info = + ConvertToMediaInfo(kMediaInfoWithContentProtection); + + MediaInfo::ProtectedContent * protected_content = + media_info.mutable_protected_content(); + MediaInfo::ProtectedContent::ContentProtectionEntry* entry = + protected_content->add_content_protection_entry(); + entry->set_uuid("9a04f079-9840-4286-ab92-e65be0885f95"); + entry->set_pssh(pssh.data(), pssh.size()); + + AddContentProtectionElements(media_info, &adaptation_set_); + ASSERT_TRUE(adaptation_set_.AddRepresentation(media_info)); + + const char kExpectedOutput[] = + "" + " " + " " + " " + "AAAAOHBzc2gBAAAAmgTweZhAQoarkuZb4IhflQAAAAERIjNEVWZ3iJkAqrvM3e7/AAAABDAxMjM=" + " " + " " + " " + ""; + + EXPECT_THAT(adaptation_set_.GetXml().get(), XmlNodeEqual(kExpectedOutput)); +} + } // namespace shaka diff --git a/packager/mpd/public/mpd_params.h b/packager/mpd/public/mpd_params.h index 92e8719bbb..002e0fcc7e 100644 --- a/packager/mpd/public/mpd_params.h +++ b/packager/mpd/public/mpd_params.h @@ -83,6 +83,10 @@ struct MpdParams { /// If enabled, allow switching between different codecs, if they have the /// same language, media type (audio, video etc) and container type. bool allow_codec_switching = false; + /// If enabled, PlayReady Object will be inserted into + /// element alongside with + /// when using PlayReady protection system. + bool include_mspr_pro = true; }; } // namespace shaka