diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 3a97599c45..9f2453c4fa 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -95,6 +95,7 @@ class EncryptionInfoEntry : public HlsEntry { public: EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method, const std::string& url, + const std::string& key_id, const std::string& iv, const std::string& key_format, const std::string& key_format_versions); @@ -106,6 +107,7 @@ class EncryptionInfoEntry : public HlsEntry { private: const MediaPlaylist::EncryptionMethod method_; const std::string url_; + const std::string key_id_; const std::string iv_; const std::string key_format_; const std::string key_format_versions_; @@ -115,12 +117,14 @@ class EncryptionInfoEntry : public HlsEntry { EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method, const std::string& url, + const std::string& key_id, const std::string& iv, const std::string& key_format, const std::string& key_format_versions) : HlsEntry(HlsEntry::EntryType::kExtKey), method_(method), url_(url), + key_id_(key_id), iv_(iv), key_format_(key_format), key_format_versions_(key_format_versions) {} @@ -133,12 +137,17 @@ std::string EncryptionInfoEntry::ToString() { method_attribute = "METHOD=SAMPLE-AES"; } else if (method_ == MediaPlaylist::EncryptionMethod::kAes128) { method_attribute = "METHOD=AES-128"; + } else if (method_ == MediaPlaylist::EncryptionMethod::kSampleAesCenc) { + method_attribute = "METHOD=SAMPLE-AES-CENC"; } else { DCHECK(method_ == MediaPlaylist::EncryptionMethod::kNone); method_attribute = "METHOD=NONE"; } std::string ext_key = "#EXT-X-KEY:" + method_attribute + ",URI=\"" + url_ + "\""; + if (!key_id_.empty()) { + ext_key += ",KEYID=" + key_id_; + } if (!iv_.empty()) { ext_key += ",IV=" + iv_; } @@ -281,17 +290,12 @@ void MediaPlaylist::RemoveOldestSegment() { void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method, const std::string& url, + const std::string& key_id, const std::string& iv, const std::string& key_format, const std::string& key_format_versions) { - if (!entries_.empty()) { - // No reason to have two consecutive EXT-X-KEY entries. Remove the previous - // one. - if (entries_.back()->type() == HlsEntry::EntryType::kExtKey) - entries_.pop_back(); - } - entries_.emplace_back(new EncryptionInfoEntry(method, url, iv, key_format, - key_format_versions)); + entries_.emplace_back(new EncryptionInfoEntry( + method, url, key_id, iv, key_format, key_format_versions)); } bool MediaPlaylist::WriteToFile(media::File* file) { diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 59e6eafbdf..f1916b768b 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -55,9 +55,10 @@ class MediaPlaylist { kPlayListSubtitle, }; enum class EncryptionMethod { - kNone, // No encryption, i.e. clear. - kAes128, // Completely encrypted using AES-CBC. - kSampleAes, // Encrypted using Sample AES method. + kNone, // No encryption, i.e. clear. + kAes128, // Completely encrypted using AES-CBC. + kSampleAes, // Encrypted using SAMPLE-AES method. + kSampleAesCenc, // 'cenc' encrypted content. }; /// @param type is the type of this media playlist. @@ -107,6 +108,7 @@ class MediaPlaylist { /// the key that can be fetched from |url|, until calling this again. /// @param method is the encryption method. /// @param url specifies where the key is i.e. the value of the URI attribute. + /// #param key_id is the default key ID for the encrypted segements. /// @param iv is the initialization vector in human readable format, i.e. the /// value for IV attribute. This may be empty. /// @param key_format is the key format, i.e. the KEYFORMAT value. This may be @@ -115,6 +117,7 @@ class MediaPlaylist { /// empty. virtual void AddEncryptionInfo(EncryptionMethod method, const std::string& url, + const std::string& key_id, const std::string& iv, const std::string& key_format, const std::string& key_format_versions); diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 950eb1f52d..34f412f514 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -112,7 +112,8 @@ TEST_F(MediaPlaylistTest, AddSegment) { TEST_F(MediaPlaylistTest, AddEncryptionInfo) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, - "http://example.com", "0xabcedf", "", ""); + "http://example.com", "", "0xabcedf", "", + ""); } TEST_F(MediaPlaylistTest, WriteToFile) { @@ -227,7 +228,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, - "http://example.com", "0x12345678", + "http://example.com", "", "0x12345678", "com.widevine", "1/2/4"); // 10 seconds. media_playlist_.AddSegment("file1.ts", 900000, 1000000); @@ -261,7 +262,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, - "http://example.com", "", "com.widevine", + "http://example.com", "", "", "com.widevine", ""); // 10 seconds. media_playlist_.AddSegment("file1.ts", 900000, 1000000); @@ -297,7 +298,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) { media_playlist_.AddSegment("file1.ts", 900000, 1000000); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, - "http://example.com", "0x12345678", + "http://example.com", "", "0x12345678", "com.widevine", "1/2/4"); media_playlist_.AddSegment("file2.ts", 2700000, 5000000); const std::string kExpectedOutput = @@ -400,5 +401,85 @@ TEST_F(MediaPlaylistTest, InitSegment) { EXPECT_TRUE(media_playlist_.WriteToFile(&file)); } +// Verify that kSampleAesCenc is handled correctly. +TEST_F(MediaPlaylistTest, SampleAesCenc) { + valid_video_media_info_.set_reference_time_scale(90000); + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + + media_playlist_.AddEncryptionInfo( + MediaPlaylist::EncryptionMethod::kSampleAesCenc, "http://example.com", "", + "0x12345678", "com.widevine", "1/2/4"); + + // 10 seconds. + media_playlist_.AddSegment("file1.ts", 900000, 1000000); + // 30 seconds. + media_playlist_.AddSegment("file2.ts", 2700000, 5000000); + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:30\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-KEY:METHOD=SAMPLE-AES-CENC," + "URI=\"http://example.com\",IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\"," + "KEYFORMAT=\"com.widevine\"\n" + "#EXTINF:10.000,\n" + "file1.ts\n" + "#EXTINF:30.000,\n" + "file2.ts\n" + "#EXT-X-ENDLIST\n"; + + MockFile file; + EXPECT_CALL(file, + Write(MatchesString(kExpectedOutput), kExpectedOutput.size())) + .WillOnce(ReturnArg<1>()); + EXPECT_TRUE(media_playlist_.WriteToFile(&file)); +} + +// Verify that multiple encryption info can be set. +TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) { + valid_video_media_info_.set_reference_time_scale(90000); + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + + media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, + "http://example.com", "", "0x12345678", + "com.widevine", "1/2/4"); + + media_playlist_.AddEncryptionInfo( + MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com", + "0xfedc", "0x12345678", "com.widevine.someother", "1"); + + // 10 seconds. + media_playlist_.AddSegment("file1.ts", 900000, 1000000); + // 30 seconds. + media_playlist_.AddSegment("file2.ts", 2700000, 5000000); + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:30\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-KEY:METHOD=SAMPLE-AES," + "URI=\"http://example.com\",IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\"," + "KEYFORMAT=\"com.widevine\"\n" + "#EXT-X-KEY:METHOD=SAMPLE-AES," + "URI=\"http://mydomain.com\",KEYID=0xfedc,IV=0x12345678," + "KEYFORMATVERSIONS=\"1\"," + "KEYFORMAT=\"com.widevine.someother\"\n" + "#EXTINF:10.000,\n" + "file1.ts\n" + "#EXTINF:30.000,\n" + "file2.ts\n" + "#EXT-X-ENDLIST\n"; + + MockFile file; + EXPECT_CALL(file, + Write(MatchesString(kExpectedOutput), kExpectedOutput.size())) + .WillOnce(ReturnArg<1>()); + EXPECT_TRUE(media_playlist_.WriteToFile(&file)); +} + } // namespace hls } // namespace shaka diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index 01b51eddde..105126d9b4 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -30,9 +30,10 @@ class MockMediaPlaylist : public MediaPlaylist { uint64_t duration, uint64_t size)); MOCK_METHOD0(RemoveOldestSegment, void()); - MOCK_METHOD5(AddEncryptionInfo, + MOCK_METHOD6(AddEncryptionInfo, void(EncryptionMethod method, const std::string& url, + const std::string& key_id, const std::string& iv, const std::string& key_format, const std::string& key_format_versions)); diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 07dde8984e..93e8beec52 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -10,10 +10,12 @@ #include "packager/base/files/file_path.h" #include "packager/base/json/json_writer.h" #include "packager/base/logging.h" +#include "packager/base/optional.h" #include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/stringprintf.h" #include "packager/hls/base/media_playlist.h" #include "packager/media/base/fixed_key_source.h" +#include "packager/media/base/protection_system_specific_info.h" #include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_pssh_data.pb.h" @@ -21,6 +23,11 @@ namespace shaka { namespace hls { namespace { + +const char kUriBase64Prefix[] = "data:text/plain;base64,"; +const char kWidevineDashIfIopUUID[] = + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; + bool IsWidevineSystemId(const std::vector& system_id) { return system_id.size() == arraysize(media::kWidevineSystemId) && std::equal(system_id.begin(), system_id.end(), @@ -69,11 +76,18 @@ void MakePathsRelativeToOutputDirectory(const std::string& output_dir, } } -bool WidevinePsshToJson(const std::vector& pssh_data, +bool WidevinePsshToJson(const std::vector& pssh_box, const std::vector& key_id, std::string* pssh_json) { + media::ProtectionSystemSpecificInfo pssh_info; + if (!pssh_info.Parse(pssh_box.data(), pssh_box.size())) { + LOG(ERROR) << "Failed to parse PSSH box."; + return false; + } + media::WidevinePsshData pssh_proto; - if (!pssh_proto.ParseFromArray(pssh_data.data(), pssh_data.size())) { + if (!pssh_proto.ParseFromArray(pssh_info.pssh_data().data(), + pssh_info.pssh_data().size())) { LOG(ERROR) << "Failed to parse protection_system_specific_data."; return false; } @@ -93,6 +107,7 @@ bool WidevinePsshToJson(const std::vector& pssh_data, pssh_dict.SetString("content_id", content_id_base64); } base::ListValue* key_ids = new base::ListValue(); + key_ids->AppendString(base::HexEncode(key_id.data(), key_id.size())); for (const std::string& id : pssh_proto.key_id()) { if (key_id.size() == id.size() && @@ -110,6 +125,72 @@ bool WidevinePsshToJson(const std::vector& pssh_data, return true; } +base::Optional StringToEncrypionMethod( + const std::string& method) { + if (method == "cenc") { + return MediaPlaylist::EncryptionMethod::kSampleAesCenc; + } else if (method == "cbcs") { + return MediaPlaylist::EncryptionMethod::kSampleAes; + } else if (method == "cbca") { + // cbca is a place holder for sample aes. + return MediaPlaylist::EncryptionMethod::kSampleAes; + } else { + return base::nullopt; + } +} + +void NotifyEncryptionToMediaPlaylist( + MediaPlaylist::EncryptionMethod encryption_method, + const std::string& uri, + const std::vector& key_id, + const std::vector& iv, + const std::string& key_format, + const std::string& key_format_version, + MediaPlaylist* media_playlist) { + std::string iv_string; + if (!iv.empty()) { + iv_string = "0x" + base::HexEncode(iv.data(), iv.size()); + } + std::string key_id_string; + if (!key_id.empty()) { + key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size()); + } + std::string key_uri_data_base64; + base::Base64Encode(uri, &key_uri_data_base64); + media_playlist->AddEncryptionInfo( + encryption_method, + kUriBase64Prefix + key_uri_data_base64, key_id_string, iv_string, + key_format, key_format_version); +} + +// Creates JSON format and the format similar to MPD. +bool HandleWidevineKeyFormats( + MediaPlaylist::EncryptionMethod encryption_method, + const std::vector& key_id, + const std::vector& iv, + const std::vector& protection_system_specific_data, + MediaPlaylist* media_playlist) { + if (encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) { + // This format allows SAMPLE-AES only. + std::string key_uri_data; + if (!WidevinePsshToJson(protection_system_specific_data, key_id, + &key_uri_data)) { + return false; + } + // This format does not have a key id field. + NotifyEncryptionToMediaPlaylist( + encryption_method, key_uri_data, + std::vector(), iv, "com.widevine", "", media_playlist); + } + + std::string pssh_as_string( + reinterpret_cast(protection_system_specific_data.data()), + protection_system_specific_data.size()); + NotifyEncryptionToMediaPlaylist(encryption_method, pssh_as_string, key_id, iv, + kWidevineDashIfIopUUID, "1", media_playlist); + return true; +} + } // namespace MediaPlaylistFactory::~MediaPlaylistFactory() {} @@ -169,11 +250,26 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info, return false; } + MediaPlaylist::EncryptionMethod encryption_method = + MediaPlaylist::EncryptionMethod::kNone; + if (media_info.protected_content().has_protection_scheme()) { + const std::string& protection_scheme = + media_info.protected_content().protection_scheme(); + base::Optional enc_method = + StringToEncrypionMethod(protection_scheme); + if (!enc_method) { + LOG(ERROR) << "Failed to recognize protection scheme " + << protection_scheme; + return false; + } + encryption_method = enc_method.value(); + } + *stream_id = sequence_number_.GetNext(); base::AutoLock auto_lock(lock_); master_playlist_->AddMediaPlaylist(media_playlist.get()); - media_playlist_map_.insert( - std::make_pair(*stream_id, std::move(media_playlist))); + stream_map_[*stream_id].reset( + new StreamEntry{std::move(media_playlist), encryption_method}); return true; } @@ -183,15 +279,15 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, uint64_t duration, uint64_t size) { base::AutoLock auto_lock(lock_); - auto result = media_playlist_map_.find(stream_id); - if (result == media_playlist_map_.end()) { + auto stream_iterator = stream_map_.find(stream_id); + if (stream_iterator == stream_map_.end()) { LOG(ERROR) << "Cannot find stream with ID: " << stream_id; return false; } const std::string relative_segment_name = MakePathRelative(segment_name, output_dir_); - auto& media_playlist = result->second; + auto& media_playlist = stream_iterator->second->media_playlist; media_playlist->AddSegment(prefix_ + relative_segment_name, duration, size); return true; } @@ -203,43 +299,36 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( const std::vector& iv, const std::vector& protection_system_specific_data) { base::AutoLock auto_lock(lock_); - auto result = media_playlist_map_.find(stream_id); - if (result == media_playlist_map_.end()) { + auto stream_iterator = stream_map_.find(stream_id); + if (stream_iterator == stream_map_.end()) { LOG(ERROR) << "Cannot find stream with ID: " << stream_id; return false; } - std::string key_format; - std::string key_uri_data; + std::unique_ptr& media_playlist = + stream_iterator->second->media_playlist; + const MediaPlaylist::EncryptionMethod encryption_method = + stream_iterator->second->encryption_method; + LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone) + << "Got encryption notification but the encryption method is NONE"; if (IsWidevineSystemId(system_id)) { - key_format = "com.widevine"; - if (!WidevinePsshToJson(protection_system_specific_data, key_id, - &key_uri_data)) { - return false; - } - } else if (IsCommonSystemId(system_id)) { - key_format = "identity"; + return HandleWidevineKeyFormats(encryption_method, + key_id, iv, protection_system_specific_data, + media_playlist.get()); + } + if (IsCommonSystemId(system_id)) { // Use key_id as the key_uri. The player needs to have custom logic to // convert it to the actual key url. + std::string key_uri_data; key_uri_data.assign(key_id.begin(), key_id.end()); - } else { - LOG(ERROR) << "Unknown system ID: " - << base::HexEncode(system_id.data(), system_id.size()); - return false; + NotifyEncryptionToMediaPlaylist(encryption_method, + key_uri_data, std::vector(), iv, + "identity", "", media_playlist.get()); + return true; } - - auto& media_playlist = result->second; - std::string iv_string; - if (!iv.empty()) { - iv_string = "0x" + base::HexEncode(iv.data(), iv.size()); - } - std::string key_uri_data_base64; - base::Base64Encode(key_uri_data, &key_uri_data_base64); - media_playlist->AddEncryptionInfo( - MediaPlaylist::EncryptionMethod::kSampleAes, - "data:text/plain;base64," + key_uri_data_base64, iv_string, key_format, - "" /* key_format_versions */); - return true; + LOG(ERROR) << "Unknown system ID: " + << base::HexEncode(system_id.data(), system_id.size()); + return false; } bool SimpleHlsNotifier::Flush() { diff --git a/packager/hls/base/simple_hls_notifier.h b/packager/hls/base/simple_hls_notifier.h index f21925a41e..411a0cdf46 100644 --- a/packager/hls/base/simple_hls_notifier.h +++ b/packager/hls/base/simple_hls_notifier.h @@ -76,12 +76,19 @@ class SimpleHlsNotifier : public HlsNotifier { private: friend class SimpleHlsNotifierTest; + struct StreamEntry { + std::unique_ptr media_playlist; + MediaPlaylist::EncryptionMethod encryption_method; + }; + const std::string prefix_; const std::string output_dir_; std::unique_ptr media_playlist_factory_; std::unique_ptr master_playlist_; - std::map> media_playlist_map_; + + // Maps to unique_ptr because StreamEntry also holds unique_ptr + std::map> stream_map_; base::AtomicSequenceNumber sequence_number_; diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 63d951d450..c98a2bccb3 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -11,6 +11,7 @@ #include "packager/hls/base/mock_media_playlist.h" #include "packager/hls/base/simple_hls_notifier.h" #include "packager/media/base/fixed_key_source.h" +#include "packager/media/base/protection_system_specific_info.h" #include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_pssh_data.pb.h" @@ -66,6 +67,9 @@ MATCHER_P(SegmentTemplateEq, expected_template, "") { return arg.segment_template() == expected_template; } +const char kCencProtectionScheme[] = "cenc"; +const char kSampleAesProtectionScheme[] = "cbca"; + } // namespace class SimpleHlsNotifierTest : public ::testing::Test { @@ -101,12 +105,13 @@ class SimpleHlsNotifierTest : public ::testing::Test { notifier->master_playlist_ = std::move(playlist); } - const std::map>& - GetMediaPlaylistMap() { - return notifier_.media_playlist_map_; - } + size_t NumRegisteredMediaPlaylists() { return notifier_.stream_map_.size(); } - uint32_t SetupStream(MockMediaPlaylist* mock_media_playlist) { + uint32_t SetupStream(const std::string& protection_scheme, + MockMediaPlaylist* mock_media_playlist) { + MediaInfo media_info; + media_info.mutable_protected_content()->set_protection_scheme( + protection_scheme); std::unique_ptr mock_master_playlist( new MockMasterPlaylist()); std::unique_ptr factory( @@ -122,7 +127,6 @@ class SimpleHlsNotifierTest : public ::testing::Test { InjectMasterPlaylist(std::move(mock_master_playlist)); InjectMediaPlaylistFactory(std::move(factory)); EXPECT_TRUE(notifier_.Init()); - MediaInfo media_info; uint32_t stream_id; EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", "groupid", &stream_id)); @@ -289,7 +293,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { uint32_t stream_id; EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8", "name", "groupid", &stream_id)); - EXPECT_EQ(1u, GetMediaPlaylistMap().size()); + EXPECT_EQ(1u, NumRegisteredMediaPlaylists()); } TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { @@ -337,7 +341,8 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - const uint32_t stream_id = SetupStream(mock_media_playlist); + const uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist); const std::vector iv(16, 0x45); @@ -346,32 +351,54 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { widevine_pssh_data.set_content_id("contentid"); const uint8_t kAnyKeyId[] = { 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, }; + std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); widevine_pssh_data.add_key_id()->assign(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); std::string widevine_pssh_data_str = widevine_pssh_data.SerializeAsString(); + EXPECT_TRUE(!widevine_pssh_data_str.empty()); std::vector pssh_data(widevine_pssh_data_str.begin(), widevine_pssh_data_str.end()); + media::ProtectionSystemSpecificInfo pssh_info; + pssh_info.set_pssh_data(pssh_data); + pssh_info.set_system_id(widevine_system_id_.data(), + widevine_system_id_.size()); + pssh_info.add_key_id(any_key_id); + const char kExpectedJson[] = "{" "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"11223344\"]," + "\"key_ids\":[\"11223344112233441122334411223344\"]," "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); + std::string expected_pssh_base64; + const std::vector pssh_box = pssh_info.CreateBox(); + base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), + &expected_pssh_base64); + EXPECT_CALL( *mock_media_playlist, - AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, + AddEncryptionInfo(_, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), StrEq("com.widevine"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, + StrEq("data:text/plain;base64," + expected_pssh_base64), + StrEq("0x11223344112233441122334411223344"), + StrEq("0x45454545454545454545454545454545"), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( - stream_id, - std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), - widevine_system_id_, iv, pssh_data)); + stream_id, any_key_id, widevine_system_id_, iv, pssh_box)); } // Verify that key_ids in pssh is optional. @@ -379,7 +406,8 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - const uint32_t stream_id = SetupStream(mock_media_playlist); + const uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist); const std::vector iv(16, 0x45); @@ -394,31 +422,54 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { const char kExpectedJson[] = "{" "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"11223344\"]," + "\"key_ids\":[\"11223344112233441122334411223344\"]," "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); + media::ProtectionSystemSpecificInfo pssh_info; + pssh_info.set_pssh_data(pssh_data); + pssh_info.set_system_id(widevine_system_id_.data(), + widevine_system_id_.size()); + + std::string expected_pssh_base64; + const std::vector pssh_box = pssh_info.CreateBox(); + base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), + &expected_pssh_base64); + EXPECT_CALL( *mock_media_playlist, - AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, + AddEncryptionInfo(_, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), StrEq("com.widevine"), _)); + EXPECT_CALL( + *mock_media_playlist, + AddEncryptionInfo( + _, + StrEq("data:text/plain;base64," + expected_pssh_base64), + StrEq("0x11223344112233441122334411223344"), + StrEq("0x45454545454545454545454545454545"), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); const uint8_t kAnyKeyId[] = { 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, }; EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), - widevine_system_id_, iv, pssh_data)); + widevine_system_id_, iv, pssh_box)); } TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - const uint32_t stream_id = SetupStream(mock_media_playlist); + const uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist); const std::vector key_id(16, 0x23); const std::vector iv(16, 0x45); @@ -431,8 +482,9 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) { EXPECT_CALL( *mock_media_playlist, AddEncryptionInfo( - MediaPlaylist::EncryptionMethod::kSampleAes, + _, StrEq("data:text/plain;base64," + expected_key_uri_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), StrEq("identity"), _)); EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); @@ -445,7 +497,8 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - uint32_t stream_id = SetupStream(mock_media_playlist); + uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist); std::vector iv(16, 0x45); @@ -453,10 +506,21 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { widevine_pssh_data.set_provider("someprovider"); const uint8_t kFirstKeyId[] = { 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, }; const uint8_t kSecondKeyId[] = { 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, }; + std::vector first_keyid(kFirstKeyId, + kFirstKeyId + arraysize(kFirstKeyId)); + std::vector second_keyid(kSecondKeyId, + kSecondKeyId + arraysize(kSecondKeyId)); + widevine_pssh_data.add_key_id()->assign(kFirstKeyId, kFirstKeyId + arraysize(kFirstKeyId)); widevine_pssh_data.add_key_id()->assign( @@ -466,40 +530,144 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { std::vector pssh_data(widevine_pssh_data_str.begin(), widevine_pssh_data_str.end()); + media::ProtectionSystemSpecificInfo pssh_info; + pssh_info.set_pssh_data(pssh_data); + pssh_info.set_system_id(widevine_system_id_.data(), + widevine_system_id_.size()); + pssh_info.add_key_id(first_keyid); + pssh_info.add_key_id(second_keyid); + const char kExpectedJson[] = "{" - "\"key_ids\":[\"22222222\",\"11111111\"]," + "\"key_ids\":[\"22222222222222222222222222222222\"," + "\"11111111111111111111111111111111\"]," "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); + std::string expected_pssh_base64; + const std::vector pssh_box = pssh_info.CreateBox(); + base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), + &expected_pssh_base64); + EXPECT_CALL( *mock_media_playlist, - AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, + AddEncryptionInfo(_, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), StrEq("com.widevine"), _)); + + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, + StrEq("data:text/plain;base64," + expected_pssh_base64), + StrEq("0x22222222222222222222222222222222"), + StrEq("0x45454545454545454545454545454545"), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); + EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, // Use the second key id here so that it will be thre first one in the // key_ids array in the JSON. - std::vector(kSecondKeyId, - kSecondKeyId + arraysize(kSecondKeyId)), - widevine_system_id_, iv, pssh_data)); + second_keyid, widevine_system_id_, iv, pssh_box)); } -TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { +// Verify that the encryption scheme set in MediaInfo is passed to +// MediaPlaylist::AddEncryptionInfo(). +TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - const uint32_t stream_id = SetupStream(mock_media_playlist); + const uint32_t stream_id = + SetupStream(kCencProtectionScheme, mock_media_playlist); + + const std::vector key_id(16, 0x23); + const std::vector iv(16, 0x45); + const std::vector dummy_pssh_data(10, 'p'); + + std::string expected_key_uri_base64; + base::Base64Encode(std::string(key_id.begin(), key_id.end()), + &expected_key_uri_base64); + + EXPECT_CALL( + *mock_media_playlist, + AddEncryptionInfo( + MediaPlaylist::EncryptionMethod::kSampleAesCenc, + StrEq("data:text/plain;base64," + expected_key_uri_base64), + StrEq(""), + StrEq("0x45454545454545454545454545454545"), StrEq("identity"), _)); + EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); +} + +// If using 'cenc' with Widevine, don't output the json form. +TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kVodPlaylist, "", "", ""); + const uint32_t stream_id = + SetupStream(kCencProtectionScheme, mock_media_playlist); + + const std::vector iv(16, 0x45); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); widevine_pssh_data.set_content_id("contentid"); const uint8_t kAnyKeyId[] = { 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, }; + std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); + widevine_pssh_data.add_key_id()->assign(kAnyKeyId, + kAnyKeyId + arraysize(kAnyKeyId)); + std::string widevine_pssh_data_str = widevine_pssh_data.SerializeAsString(); + + EXPECT_TRUE(!widevine_pssh_data_str.empty()); + std::vector pssh_data(widevine_pssh_data_str.begin(), + widevine_pssh_data_str.end()); + + media::ProtectionSystemSpecificInfo pssh_info; + pssh_info.set_pssh_data(pssh_data); + pssh_info.set_system_id(widevine_system_id_.data(), + widevine_system_id_.size()); + pssh_info.add_key_id(any_key_id); + + std::string expected_pssh_base64; + const std::vector pssh_box = pssh_info.CreateBox(); + base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), + &expected_pssh_base64); + + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, + StrEq("data:text/plain;base64," + expected_pssh_base64), + StrEq("0x11223344112233441122334411223344"), + StrEq("0x45454545454545454545454545454545"), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); + EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + stream_id, any_key_id, widevine_system_id_, iv, pssh_box)); +} + +TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kVodPlaylist, "", "", ""); + const uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + + media::WidevinePsshData widevine_pssh_data; + widevine_pssh_data.set_provider("someprovider"); + widevine_pssh_data.set_content_id("contentid"); + const uint8_t kAnyKeyId[] = { + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, + }; + std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); widevine_pssh_data.add_key_id()->assign(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); std::string widevine_pssh_data_str = widevine_pssh_data.SerializeAsString(); @@ -510,22 +678,44 @@ TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { const char kExpectedJson[] = "{" "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"11223344\"]," + "\"key_ids\":[\"11223344112233441122334411223344\"]," "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); + media::ProtectionSystemSpecificInfo pssh_info; + pssh_info.set_pssh_data(pssh_data); + pssh_info.set_system_id(widevine_system_id_.data(), + widevine_system_id_.size()); + pssh_info.add_key_id(any_key_id); + EXPECT_CALL( *mock_media_playlist, - AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, + AddEncryptionInfo(_, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq(""), StrEq("com.widevine"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, + StrEq("data:text/plain;base64," + "AAAAS3Bzc2gAAAAA7e+" + "LqXnWSs6jyCfc1R0h7QAAACsSEBEiM0QRIjNEESIzRBEiM0QaDHNvb" + "WVwcm92aWRlciIJY29udGVudGlk"), + StrEq("0x11223344112233441122334411223344"), StrEq(""), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); + std::vector pssh_as_vec = pssh_info.CreateBox(); + std::string pssh_in_string(pssh_as_vec.begin(), pssh_as_vec.end()); + std::string base_64_encoded_pssh; + base::Base64Encode(pssh_in_string, &base_64_encoded_pssh); + LOG(INFO) << base_64_encoded_pssh; + std::vector empty_iv; EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), - widevine_system_id_, empty_iv, pssh_data)); + widevine_system_id_, empty_iv, pssh_info.CreateBox())); } TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index 140d9da6e0..493395802d 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -84,6 +84,7 @@ message MediaInfo { optional bytes default_key_id = 1; repeated ContentProtectionEntry content_protection_entry = 2; // 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']; }