diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index c2a78e7020..af96d6956c 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -33,24 +33,25 @@ void MpdNotifyMuxerListener::SetContentProtectionSchemeIdUri( } void MpdNotifyMuxerListener::OnEncryptionInfoReady( + bool is_initial_encryption_info, const std::string& content_protection_uuid, const std::string& content_protection_name_version, - const std::vector& default_key_id, + const std::vector& key_id, const std::vector& pssh) { - if (mpd_notifier_->dash_profile() == kLiveProfile) { - bool updated = mpd_notifier_->NotifyEncryptionUpdate(notification_id_, - default_key_id, pssh); - LOG_IF(WARNING, !updated) << "Failed to update pssh."; + if (is_initial_encryption_info) { + LOG_IF(WARNING, is_encrypted_) + << "Updating initial encryption information."; + content_protection_uuid_ = content_protection_uuid; + content_protection_name_version_ = content_protection_name_version; + default_key_id_.assign(key_id.begin(), key_id.end()); + pssh_.assign(pssh.begin(), pssh.end()); + is_encrypted_ = true; return; } - LOG_IF(WARNING, is_encrypted_) << "Updating encryption information, but key " - "rotation for VOD is not supported."; - content_protection_uuid_ = content_protection_uuid; - content_protection_name_version_ = content_protection_name_version; - default_key_id_.assign(default_key_id.begin(), default_key_id.end()); - pssh_.assign(pssh.begin(), pssh.end()); - is_encrypted_ = true; + bool updated = mpd_notifier_->NotifyEncryptionUpdate( + notification_id_, content_protection_uuid, key_id, pssh); + LOG_IF(WARNING, !updated) << "Failed to update encryption info."; } void MpdNotifyMuxerListener::OnMediaStart( diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 89b2592aa4..535fbf40ac 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -28,7 +28,7 @@ class MpdNotifyMuxerListener : public MuxerListener { public: /// @param mpd_notifier must be initialized, i.e mpd_notifier->Init() must be /// called. - MpdNotifyMuxerListener(MpdNotifier* mpd_notifier); + explicit MpdNotifyMuxerListener(MpdNotifier* mpd_notifier); virtual ~MpdNotifyMuxerListener(); /// If the stream is encrypted use this as 'schemeIdUri' attribute for @@ -38,9 +38,10 @@ class MpdNotifyMuxerListener : public MuxerListener { /// @name MuxerListener implementation overrides. /// @{ virtual void OnEncryptionInfoReady( + bool is_initial_encryption_info, const std::string& content_protection_uuid, const std::string& content_protection_name_version, - const std::vector& default_key_id, + const std::vector& key_id, const std::vector& pssh) OVERRIDE; virtual void OnMediaStart(const MuxerOptions& muxer_options, const StreamInfo& stream_info, diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 701af21002..db5caef88b 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -27,6 +27,15 @@ namespace edash_packager { namespace { +// Can be any string, we just want to check that it is preserved in the +// protobuf. +const char kTestUUID[] = "somebogusuuid"; +const char kDrmName[] = "drmname"; +const char kDefaultKeyId[] = "defaultkeyid"; +const char kPssh[] = "pssh"; +const bool kInitialEncryptionInfo = true; +const bool kNonInitialEncryptionInfo = false; + // TODO(rkuroiwa): This is copied from mpd_builder_test_helper.cc. Make a // common target that only has mpd_builder_test_helper and its dependencies // so the two test targets can share this. @@ -37,6 +46,18 @@ MediaInfo ConvertToMediaInfo(const std::string& media_info_string) { return media_info; } +void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) { + muxer_options->single_segment = false; + muxer_options->segment_duration = 10.0; + muxer_options->fragment_duration = 10.0; + muxer_options->segment_sap_aligned = true; + muxer_options->fragment_sap_aligned = true; + muxer_options->num_subsegments_per_sidx = 0; + muxer_options->output_file_name = "liveinit.mp4"; + muxer_options->segment_template = "live-$NUMBER$.mp4"; + muxer_options->temp_dir.clear(); +} + } // namespace namespace media { @@ -44,9 +65,14 @@ namespace media { class MpdNotifyMuxerListenerTest : public ::testing::Test { public: - // Set up objects for VOD profile. void SetupForVod() { notifier_.reset(new MockMpdNotifier(kOnDemandProfile)); + listener_.reset( + new MpdNotifyMuxerListener(notifier_.get())); + } + + void SetupForLive() { + notifier_.reset(new MockMpdNotifier(kLiveProfile)); listener_.reset(new MpdNotifyMuxerListener(notifier_.get())); } @@ -124,12 +150,6 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) { scoped_refptr video_stream_info = CreateVideoStreamInfo(video_params); - // Can be anystring, we just want to check that it is preserved in the - // protobuf. - const char kTestUUID[] = "somebogusuuid"; - const char kDrmName[] = "drmname"; - const char kDefaultKeyId[] = "defaultkeyid"; - const char kPssh[] = "pssh"; const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1); const std::vector pssh(kPssh, kPssh + arraysize(kPssh) - 1); @@ -146,7 +166,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) { "}\n"; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - listener_->OnEncryptionInfoReady(kTestUUID, kDrmName, default_key_id, pssh); + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID, kDrmName, + default_key_id, pssh); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, @@ -244,7 +265,137 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } -// TODO(rkuroiwa): Add tests for live. +// Live without key rotation. Note that OnEncryptionInfoReady() is called before +// OnMediaStart() but no more calls. +TEST_F(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { + SetupForLive(); + MuxerOptions muxer_options; + SetDefaultLiveMuxerOptionsValues(&muxer_options); + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + scoped_refptr video_stream_info = + CreateVideoStreamInfo(video_params); + + const char kExpectedMediaInfo[] = + "video_info {\n" + " codec: \"avc1.010101\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "init_segment_name: \"liveinit.mp4\"\n" + "segment_template: \"live-$NUMBER$.mp4\"\n" + "reference_time_scale: 1000\n" + "container_type: CONTAINER_MP4\n" + "protected_content {\n" + " default_key_id: \"defaultkeyid\"\n" + " content_protection_entry {\n" + " uuid: \"somebogusuuid\"\n" + " name_version: \"drmname\"\n" + " pssh: \"pssh\"\n" + " }\n" + "}\n"; + + const uint64_t kStartTime1 = 0u; + const uint64_t kDuration1 = 1000u; + const uint64_t kSegmentFileSize1 = 29812u; + const uint64_t kStartTime2 = 1001u; + const uint64_t kDuration2 = 3787u; + const uint64_t kSegmentFileSize2 = 83743u; + const std::vector default_key_id( + kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1); + const std::vector pssh(kPssh, kPssh + arraysize(kPssh) - 1); + + InSequence s; + EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, + NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) + .Times(1); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, Flush()); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, Flush()); + + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID, kDrmName, + default_key_id, pssh); + listener_->OnMediaStart(muxer_options, *video_stream_info, + kDefaultReferenceTimeScale, + MuxerListener::kContainerMp4); + listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2); + ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); + + EXPECT_CALL(*notifier_, Flush()).Times(0); + FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); +} + +// Live with key rotation. Note that OnEncryptionInfoReady() is called before +// and after OnMediaStart(). +TEST_F(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { + SetupForLive(); + MuxerOptions muxer_options; + SetDefaultLiveMuxerOptionsValues(&muxer_options); + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + scoped_refptr video_stream_info = + CreateVideoStreamInfo(video_params); + + // Note that this media info has protected_content with default key id. + const char kExpectedMediaInfo[] = + "video_info {\n" + " codec: \"avc1.010101\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "init_segment_name: \"liveinit.mp4\"\n" + "segment_template: \"live-$NUMBER$.mp4\"\n" + "reference_time_scale: 1000\n" + "container_type: CONTAINER_MP4\n" + "protected_content {\n" + " default_key_id: \"defaultkeyid\"\n" + "}\n"; + + const uint64_t kStartTime1 = 0u; + const uint64_t kDuration1 = 1000u; + const uint64_t kSegmentFileSize1 = 29812u; + const uint64_t kStartTime2 = 1001u; + const uint64_t kDuration2 = 3787u; + const uint64_t kSegmentFileSize2 = 83743u; + const std::vector default_key_id( + kDefaultKeyId, kDefaultKeyId + arraysize(kDefaultKeyId) - 1); + const std::vector pssh(kPssh, kPssh + arraysize(kPssh) - 1); + + InSequence s; + EXPECT_CALL(*notifier_, + NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) + .Times(1); + EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, Flush()); + EXPECT_CALL(*notifier_, + NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, Flush()); + + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, "", "", + default_key_id, std::vector()); + listener_->OnMediaStart(muxer_options, *video_stream_info, + kDefaultReferenceTimeScale, + MuxerListener::kContainerMp4); + listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, kTestUUID, + kDrmName, std::vector(), pssh); + listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2); + ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); + + EXPECT_CALL(*notifier_, Flush()).Times(0); + FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); +} } // namespace media } // namespace edash_packager diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index 057deacac8..2003a106eb 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -13,6 +13,7 @@ #include +#include #include namespace edash_packager { @@ -42,17 +43,25 @@ class MuxerListener { // |content_protection_uuid| is one of the UUIDs listed here // http://dashif.org/identifiers/protection/. This should be in human // readable form. + // |is_initial_encryption_info| is true if this is the first encryption info + // for the media. + // In general, this flag should always be true for non-key-rotated media and + // should be called only once. // |content_protection_name_version| is the DRM system and version name. + // |key_id| is the key ID for the media. + // The format should be a vector of uint8_t, i.e. not (necessarily) human + // readable hex string. // For ISO BMFF (MP4) media: - // |default_key_id| is the default_KID in 'tenc' box. The format should - // be a vector of uint8_t, i.e. not (necessarily) human readable hex string. + // If |is_initial_encryption_info| is true then |key_id| is the default_KID in + // 'tenc' box. + // If |is_initial_encryption_info| is false then |key_id| is the new key ID + // for the for the next crypto period. // |pssh| is the whole 'pssh' box. - // This method may be called multiple times to notify the event handler that - // the encryption info has changed. virtual void OnEncryptionInfoReady( + bool is_initial_encryption_info, const std::string& content_protection_uuid, const std::string& content_protection_name_version, - const std::vector& default_key_id, + const std::vector& key_id, const std::vector& pssh) = 0; // Called when muxing starts. diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index bf63e0a3e8..fffeff6728 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -30,10 +30,14 @@ void VodMediaInfoDumpMuxerListener::SetContentProtectionSchemeIdUri( } void VodMediaInfoDumpMuxerListener::OnEncryptionInfoReady( + bool is_initial_encryption_info, const std::string& content_protection_uuid, const std::string& content_protection_name_version, const std::vector& default_key_id, const std::vector& pssh) { + LOG_IF(WARNING, !is_initial_encryption_info) + << "Updating (non initial) encryption info is not supported by " + "this module."; content_protection_uuid_ = content_protection_uuid; content_protection_name_version_ = content_protection_name_version; default_key_id_.assign(default_key_id.begin(), default_key_id.end()); diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 552d822d1f..a4add0b6bf 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -37,6 +37,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { /// @name MuxerListener implementation overrides. /// @{ virtual void OnEncryptionInfoReady( + bool is_initial_encryption_info, const std::string& content_protection_uuid, const std::string& content_protection_name_version, const std::vector& default_key_id, diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index b7e27da8c9..8d73800d51 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -33,6 +33,7 @@ const uint8_t kInvalidPssh[] = { // format but the protobof generation shouldn't care. const char kTestUUID[] = "myuuid"; const char kTestContentProtectionName[] = "MyContentProtection version 1"; +const bool kInitialEncryptionInfo = true; } // namespace namespace edash_packager { @@ -89,7 +90,8 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { std::vector invalid_pssh(kInvalidPssh, kInvalidPssh + arraysize(kInvalidPssh)); - listener_->OnEncryptionInfoReady(kTestUUID, kTestContentProtectionName, + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, kTestUUID, + kTestContentProtectionName, bogus_default_key_id, invalid_pssh); } listener_->OnMediaStart(muxer_options, stream_info, kReferenceTimeScale, diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index 0a0c570ab9..3e4a389bcb 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -13,6 +13,10 @@ namespace edash_packager { namespace media { namespace mp4 { +namespace { +const bool kInitialEncryptionInfo = false; +} // namespace + KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, TrackFragment* traf, KeySource* encryption_key_source, @@ -62,6 +66,7 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption( if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady( + !kInitialEncryptionInfo, encryption_key_source_->UUID(), encryption_key_source_->SystemName(), encryption_key()->key_id, encryption_key()->pssh); } diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 742598080c..bd8941fe6e 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -33,6 +33,14 @@ const size_t kCencKeyIdSize = 16u; // The version of cenc implemented here. CENC 4. const int kCencSchemeVersion = 0x00010000; +// The default KID for key rotation is all 0s. +const uint8_t kKeyRotationDefaultKeyId[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; +COMPILE_ASSERT(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize, + cenc_key_id_must_be_size_16); + uint64_t Rescale(uint64_t time_in_old_scale, uint32_t old_scale, uint32_t new_scale) { @@ -81,16 +89,6 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key, } } -void GenerateEncryptedSampleEntryForKeyRotation( - double clear_lead_in_seconds, - SampleDescription* description) { - // Fill encrypted sample entry with default key. - EncryptionKey encryption_key; - encryption_key.key_id.assign(kCencKeyIdSize, 0); - GenerateEncryptedSampleEntry( - encryption_key, clear_lead_in_seconds, description); -} - uint8_t GetNaluLengthSize(const StreamInfo& stream_info) { if (stream_info.stream_type() != kStreamVideo) return 0; @@ -127,8 +125,7 @@ Segmenter::Segmenter(const MuxerOptions& options, progress_listener_(NULL), progress_target_(0), accumulated_progress_(0), - sample_duration_(0u) { -} + sample_duration_(0u) {} Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); } @@ -147,6 +144,9 @@ Status Segmenter::Initialize(const std::vector& streams, moof_->tracks.resize(streams.size()); segment_durations_.resize(streams.size()); fragmenters_.resize(streams.size()); + const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0; + const bool kInitialEncryptionInfo = true; + for (uint32_t i = 0; i < streams.size(); ++i) { stream_map_[streams[i]] = i; moof_->tracks[i].header.track_id = i + 1; @@ -166,10 +166,20 @@ Status Segmenter::Initialize(const std::vector& streams, SampleDescription& description = moov_->tracks[i].media.information.sample_table.description; - const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0; if (key_rotation_enabled) { - GenerateEncryptedSampleEntryForKeyRotation(clear_lead_in_seconds, - &description); + // Fill encrypted sample entry with default key. + EncryptionKey encryption_key; + encryption_key.key_id.assign( + kKeyRotationDefaultKeyId, + kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId)); + GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds, + &description); + if (muxer_listener_) { + muxer_listener_->OnEncryptionInfoReady( + kInitialEncryptionInfo, encryption_key_source->UUID(), + encryption_key_source->SystemName(), encryption_key.key_id, + std::vector()); + } fragmenters_[i] = new KeyRotationFragmenter( moof_.get(), @@ -189,8 +199,8 @@ Status Segmenter::Initialize(const std::vector& streams, if (!status.ok()) return status; - GenerateEncryptedSampleEntry( - *encryption_key, clear_lead_in_seconds, &description); + GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds, + &description); // One and only one pssh box is needed. if (moov_->pssh.empty()) { @@ -200,6 +210,7 @@ Status Segmenter::Initialize(const std::vector& streams, // Also only one default key id. if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady( + kInitialEncryptionInfo, encryption_key_source->UUID(), encryption_key_source->SystemName(), encryption_key->key_id, encryption_key->pssh); } diff --git a/packager/mpd/base/dash_iop_mpd_notifier.cc b/packager/mpd/base/dash_iop_mpd_notifier.cc index cea2156fb2..c9814cbcb1 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier.cc @@ -128,6 +128,7 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id, bool DashIopMpdNotifier::NotifyEncryptionUpdate( uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) { base::AutoLock auto_lock(lock_); @@ -140,7 +141,7 @@ bool DashIopMpdNotifier::NotifyEncryptionUpdate( AdaptationSet* adaptation_set_for_representation = representation_id_to_adaptation_set_[it->second->id()]; adaptation_set_for_representation->UpdateContentProtectionPssh( - Uint8VectorToBase64(new_pssh)); + drm_uuid, Uint8VectorToBase64(new_pssh)); return true; } diff --git a/packager/mpd/base/dash_iop_mpd_notifier.h b/packager/mpd/base/dash_iop_mpd_notifier.h index 1e0e1d4ce0..67f3624735 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.h +++ b/packager/mpd/base/dash_iop_mpd_notifier.h @@ -48,6 +48,7 @@ class DashIopMpdNotifier : public MpdNotifier { uint64_t size) OVERRIDE; virtual bool NotifyEncryptionUpdate( uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) OVERRIDE; virtual bool AddContentProtectionElement( diff --git a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc index e01a2a03c4..727d752a82 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc @@ -720,9 +720,10 @@ TEST_P(DashIopMpdNotifierTest, UpdateEncryption) { const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U="; EXPECT_CALL(*default_mock_adaptation_set_, - UpdateContentProtectionPssh(StrEq(kBogusNewPsshInBase64))); + UpdateContentProtectionPssh(StrEq("myuuid"), + StrEq(kBogusNewPsshInBase64))); EXPECT_TRUE(notifier.NotifyEncryptionUpdate( - container_id, std::vector(), kBogusNewPsshVector)); + container_id, "myuuid", std::vector(), kBogusNewPsshVector)); } INSTANTIATE_TEST_CASE_P(StaticAndDynamic, diff --git a/packager/mpd/base/mock_mpd_builder.h b/packager/mpd/base/mock_mpd_builder.h index 716d745322..8696c424b8 100644 --- a/packager/mpd/base/mock_mpd_builder.h +++ b/packager/mpd/base/mock_mpd_builder.h @@ -35,7 +35,8 @@ class MockAdaptationSet : public AdaptationSet { MOCK_METHOD1(AddRepresentation, Representation*(const MediaInfo& media_info)); MOCK_METHOD1(AddContentProtectionElement, void(const ContentProtectionElement& element)); - MOCK_METHOD1(UpdateContentProtectionPssh, void(const std::string& pssh)); + MOCK_METHOD2(UpdateContentProtectionPssh, + void(const std::string& drm_uuid, const std::string& pssh)); MOCK_METHOD1(AddRole, void(AdaptationSet::Role role)); MOCK_METHOD1(SetGroup, void(int group_number)); @@ -54,7 +55,8 @@ class MockRepresentation : public Representation { MOCK_METHOD1(AddContentProtectionElement, void(const ContentProtectionElement& element)); - MOCK_METHOD1(UpdateContentProtectionPssh, void(const std::string& pssh)); + MOCK_METHOD2(UpdateContentProtectionPssh, + void(const std::string& drm_uuid, const std::string& pssh)); MOCK_METHOD3(AddNewSegment, void(uint64_t start_time, uint64_t duration, uint64_t size)); MOCK_METHOD1(SetSampleDuration, void(uint32_t sample_duration)); diff --git a/packager/mpd/base/mock_mpd_notifier.h b/packager/mpd/base/mock_mpd_notifier.h index 70bce62f89..262c3519d3 100644 --- a/packager/mpd/base/mock_mpd_notifier.h +++ b/packager/mpd/base/mock_mpd_notifier.h @@ -31,8 +31,9 @@ class MockMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size)); - MOCK_METHOD3(NotifyEncryptionUpdate, + MOCK_METHOD4(NotifyEncryptionUpdate, bool(uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh)); MOCK_METHOD2( diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 8e61df71c0..a8717dfb91 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -697,10 +697,11 @@ void AdaptationSet::AddContentProtectionElement( RemoveDuplicateAttributes(&content_protection_elements_.back()); } -void AdaptationSet::UpdateContentProtectionPssh( - const std::string& pssh) { +void AdaptationSet::UpdateContentProtectionPssh(const std::string& drm_uuid, + const std::string& pssh) { base::AutoLock scoped_lock(lock_); - UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_); + UpdateContentProtectionPsshHelper(drm_uuid, pssh, + &content_protection_elements_); } void AdaptationSet::AddRole(Role role) { @@ -1041,10 +1042,11 @@ void Representation::AddContentProtectionElement( RemoveDuplicateAttributes(&content_protection_elements_.back()); } -void Representation::UpdateContentProtectionPssh( - const std::string& pssh) { +void Representation::UpdateContentProtectionPssh(const std::string& drm_uuid, + const std::string& pssh) { base::AutoLock scoped_lock(lock_); - UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_); + UpdateContentProtectionPsshHelper(drm_uuid, pssh, + &content_protection_elements_); } void Representation::AddNewSegment(uint64_t start_time, diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 3eaaf0beb0..76393a2e59 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -189,14 +189,19 @@ class AdaptationSet { virtual void AddContentProtectionElement( const ContentProtectionElement& element); - /// Update the element for MP4 specific ContentProtection element. + /// Update the 'cenc:pssh' element for @a drm_uuid ContentProtection element. /// If the element does not exist, this will add one. + /// @param drm_uuid is the UUID of the DRM for encryption. /// @param pssh is the content of element. /// Note that DASH IF IOP mentions that this should be base64 encoded /// string of the whole pssh box. - /// @attention This might get removed once DASH IF IOP specification writes - /// a clear guideline on how to handle key rotation. - virtual void UpdateContentProtectionPssh(const std::string& pssh); + /// @attention This might get removed once DASH IF IOP specification makes a + /// a clear guideline on how to handle key rotation. Also to get + /// this working with shaka-player, this method *DOES NOT* update + /// the PSSH element. Instead, it removes the element regardless of + /// the content of @a pssh. + virtual void UpdateContentProtectionPssh(const std::string& drm_uuid, + const std::string& pssh); /// Set the Role element for this AdaptationSet. /// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'. @@ -445,14 +450,19 @@ class Representation { virtual void AddContentProtectionElement( const ContentProtectionElement& element); - /// Update the 'cenc:pssh' element for mp4 specific ContentProtection element. + /// Update the 'cenc:pssh' element for @a drm_uuid ContentProtection element. /// If the element does not exist, this will add one. + /// @param drm_uuid is the UUID of the DRM for encryption. /// @param pssh is the content of element. /// Note that DASH IF IOP mentions that this should be base64 encoded /// string of the whole pssh box. /// @attention This might get removed once DASH IF IOP specification makes a - /// a clear guideline on how to handle key rotation. - virtual void UpdateContentProtectionPssh(const std::string& pssh); + /// a clear guideline on how to handle key rotation. Also to get + /// this working with shaka-player, this method *DOES NOT* update + /// the PSSH element. Instead, it removes the element regardless of + /// the content of @a pssh. + virtual void UpdateContentProtectionPssh(const std::string& drm_uuid, + const std::string& pssh); /// Add a media (sub)segment to the representation. /// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 9e8433d3f5..edaa8d0f29 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -1265,7 +1265,8 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) { "}\n" "container_type: 1\n"; ContentProtectionElement content_protection; - content_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011"; + content_protection.scheme_id_uri = + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; content_protection.value = "some value"; Element pssh; pssh.name = "cenc:pssh"; @@ -1292,7 +1293,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) { " " " " " any value" " " @@ -1306,7 +1307,8 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) { ASSERT_TRUE(mpd_.ToString(&mpd_output)); EXPECT_TRUE(XmlEqual(kExpectedOutput1, mpd_output)); - video_adaptation_set->UpdateContentProtectionPssh("new pssh value"); + video_adaptation_set->UpdateContentProtectionPssh( + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", "new pssh value"); const char kExpectedOutput2[] = "\n" "" " " - " new pssh value" + // TODO(rkuroiwa): Commenting this out for now because we want to remove + // the PSSH from the MPD. Uncomment this when the player supports updating + // pssh. + //" new pssh value" + " " + " " + " " + " " + ""; + ASSERT_TRUE(mpd_.ToString(&mpd_output)); + EXPECT_TRUE(XmlEqual(kExpectedOutput2, mpd_output)); +} + +// Verify that if the ContentProtection element for the DRM without +// element is updated via UpdateContentProtectionPssh(), the element gets added. +// TODO(rkuroiwa): Until the player supports PSSH update, we remove the pssh +// element. Rename this test once it is supported. +TEST_F(CommonMpdBuilderTest, UpdateToRemovePsshElement) { + const char kVideoMediaInfo1080p[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1920\n" + " height: 1080\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + "}\n" + "container_type: 1\n"; + ContentProtectionElement content_protection; + content_protection.scheme_id_uri = + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; + content_protection.value = "some value"; + + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1080p))); + video_adaptation_set->AddContentProtectionElement(content_protection); + + const char kExpectedOutput1[] = + "\n" + "" + " " + " " + " " + " " + " " + " " + " " + ""; + std::string mpd_output; + ASSERT_TRUE(mpd_.ToString(&mpd_output)); + EXPECT_TRUE(XmlEqual(kExpectedOutput1, mpd_output)); + + video_adaptation_set->UpdateContentProtectionPssh( + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + "added pssh value"); + const char kExpectedOutput2[] = + "\n" + "" + " " + " " + " " + // TODO(rkuroiwa): Commenting this out for now because we want to remove + // teh PSSH from the MPD. Uncomment this when the player supports updating + // pssh. + //" added pssh value" " " " +#include #include #include "packager/base/macros.h" @@ -78,11 +79,13 @@ class MpdNotifier { /// This may be called whenever the key has to change, e.g. key rotation. /// @param container_id Container ID obtained from calling /// NotifyNewContainer(). + /// @param drm_uuid is the UUID of the DRM for encryption. /// @param new_key_id is the new key ID for the key. /// @param new_pssh is the new pssh box (including the header). /// @attention This might change or get removed once DASH IF IOP specification /// writes a clear guideline on how to handle key rotation. virtual bool NotifyEncryptionUpdate(uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) = 0; diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 9dc1b45acc..07ebeff1ff 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -133,40 +133,52 @@ bool HexToUUID(const std::string& data, std::string* uuid_format) { } void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, const std::string& pssh, - std::list* conetent_protection_elements) { + std::list* content_protection_elements) { + const std::string drm_uuid_schemd_id_uri_form = "urn:uuid:" + drm_uuid; for (std::list::iterator protection = - conetent_protection_elements->begin(); - protection != conetent_protection_elements->end(); ++protection) { - if (protection->scheme_id_uri != kEncryptedMp4Scheme) + 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) { - subelement->content = pssh; + // 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 MP4 specific - // ContentProtection. Add it. - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = pssh; + + // 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 MP4 specific ContentProtection does not exist. + // Reaching here means that ContentProtection for the DRM does not exist. // Add it. ContentProtectionElement content_protection; - content_protection.scheme_id_uri = kEncryptedMp4Scheme; - content_protection.value = kEncryptedMp4Value; - Element cenc_pssh; - cenc_pssh.name = kPsshElementName; - cenc_pssh.content = pssh; - content_protection.subelements.push_back(cenc_pssh); - conetent_protection_elements->push_back(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; } diff --git a/packager/mpd/base/mpd_utils.h b/packager/mpd/base/mpd_utils.h index f1bda2b343..50d8268e5e 100644 --- a/packager/mpd/base/mpd_utils.h +++ b/packager/mpd/base/mpd_utils.h @@ -58,11 +58,12 @@ bool OnlyOneTrue(bool b1, bool b2, bool b3); /// @param uuid_format is the UUID format of the input. bool HexToUUID(const std::string& data, std::string* uuid_format); -// Update the element for MP4 specific ContentProtection element. +// Update the element for |drm_uuid| ContentProtection element. // If the element does not exist, this will add one. void UpdateContentProtectionPsshHelper( + const std::string& drm_uuid, const std::string& pssh, - std::list* conetent_protection_elements); + std::list* content_protection_elements); /// Adds elements specified by @a media_info to /// @a adaptation_set. diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index bf659460d3..42a4ec8863 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -99,6 +99,7 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, bool SimpleMpdNotifier::NotifyEncryptionUpdate( uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) { base::AutoLock auto_lock(lock_); @@ -107,7 +108,8 @@ bool SimpleMpdNotifier::NotifyEncryptionUpdate( LOG(ERROR) << "Unexpected container_id: " << container_id; return false; } - it->second->UpdateContentProtectionPssh(Uint8VectorToBase64(new_pssh)); + it->second->UpdateContentProtectionPssh(drm_uuid, + Uint8VectorToBase64(new_pssh)); return true; } diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index fae28307c9..1a70d168f1 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -49,6 +49,7 @@ class SimpleMpdNotifier : public MpdNotifier { uint64_t size) OVERRIDE; virtual bool NotifyEncryptionUpdate( uint32_t container_id, + const std::string& drm_uuid, const std::vector& new_key_id, const std::vector& new_pssh) OVERRIDE; virtual bool AddContentProtectionElement( diff --git a/packager/mpd/base/simple_mpd_notifier_unittest.cc b/packager/mpd/base/simple_mpd_notifier_unittest.cc index 7cd169e78f..c7289ae522 100644 --- a/packager/mpd/base/simple_mpd_notifier_unittest.cc +++ b/packager/mpd/base/simple_mpd_notifier_unittest.cc @@ -286,9 +286,10 @@ TEST_P(SimpleMpdNotifierTest, UpdateEncryption) { const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U="; EXPECT_CALL(*mock_representation, - UpdateContentProtectionPssh(StrEq(kBogusNewPsshInBase64))); + UpdateContentProtectionPssh(StrEq("myuuid"), + StrEq(kBogusNewPsshInBase64))); EXPECT_TRUE(notifier.NotifyEncryptionUpdate( - container_id, std::vector(), kBogusNewPsshVector)); + container_id, "myuuid", std::vector(), kBogusNewPsshVector)); } INSTANTIATE_TEST_CASE_P(StaticAndDynamic,