diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index fb71f0032f..c2a78e7020 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -37,6 +37,15 @@ void MpdNotifyMuxerListener::OnEncryptionInfoReady( const std::string& content_protection_name_version, const std::vector& default_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."; + 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()); diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 8f9731d861..fddf4b7e05 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -51,6 +51,10 @@ class MockMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size)); + MOCK_METHOD3(NotifyEncryptionUpdate, + bool(uint32_t container_id, + const std::vector& new_key_id, + const std::vector& new_pssh)); MOCK_METHOD2( AddContentProtectionElement, bool(uint32_t container_id, diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index 6712563d6e..057deacac8 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -47,6 +47,8 @@ class MuxerListener { // |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. // |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( const std::string& content_protection_uuid, const std::string& content_protection_name_version, diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index 4b91264c8a..0a0c570ab9 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -19,7 +19,8 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, KeySource::TrackType track_type, int64_t crypto_period_duration, int64_t clear_time, - uint8_t nalu_length_size) + uint8_t nalu_length_size, + MuxerListener* muxer_listener) : EncryptingFragmenter(traf, scoped_ptr(new EncryptionKey()), clear_time, @@ -28,7 +29,8 @@ KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof, encryption_key_source_(encryption_key_source), track_type_(track_type), crypto_period_duration_(crypto_period_duration), - prev_crypto_period_index_(-1) { + prev_crypto_period_index_(-1), + muxer_listener_(muxer_listener) { DCHECK(moof); DCHECK(encryption_key_source); } @@ -58,6 +60,12 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption( DCHECK(encryption_key()); moof_->pssh[0].raw_box = encryption_key()->pssh; + if (muxer_listener_) { + muxer_listener_->OnEncryptionInfoReady( + encryption_key_source_->UUID(), encryption_key_source_->SystemName(), + encryption_key()->key_id, encryption_key()->pssh); + } + // Skip the following steps if the current fragment is not going to be // encrypted. 'pssh' box needs to be included in the fragment, which is // performed above, regardless of whether the fragment is encrypted. This is diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.h b/packager/media/formats/mp4/key_rotation_fragmenter.h index c82d5b6218..1c8ff218d9 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.h +++ b/packager/media/formats/mp4/key_rotation_fragmenter.h @@ -8,6 +8,7 @@ #define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_ #include "packager/media/base/key_source.h" +#include "packager/media/event/muxer_listener.h" #include "packager/media/formats/mp4/encrypting_fragmenter.h" namespace edash_packager { @@ -32,13 +33,16 @@ class KeyRotationFragmenter : public EncryptingFragmenter { /// track's timescale. /// @param nalu_length_size NAL unit length size, in bytes, for subsample /// encryption. + /// @param muxer_listener is a pointer to MuxerListener for notifying + /// muxer related events. This may be null. KeyRotationFragmenter(MovieFragment* moof, TrackFragment* traf, KeySource* encryption_key_source, KeySource::TrackType track_type, int64_t crypto_period_duration, int64_t clear_time, - uint8_t nalu_length_size); + uint8_t nalu_length_size, + MuxerListener* muxer_listener); virtual ~KeyRotationFragmenter(); protected: @@ -56,6 +60,9 @@ class KeyRotationFragmenter : public EncryptingFragmenter { const int64_t crypto_period_duration_; size_t prev_crypto_period_index_; + // For notifying new pssh boxes to the event handler. + MuxerListener* const muxer_listener_; + DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter); }; diff --git a/packager/media/formats/mp4/mp4.gyp b/packager/media/formats/mp4/mp4.gyp index cfc1ab84da..f4e15d933e 100644 --- a/packager/media/formats/mp4/mp4.gyp +++ b/packager/media/formats/mp4/mp4.gyp @@ -58,6 +58,7 @@ 'dependencies': [ '../../../third_party/openssl/openssl.gyp:openssl', '../../base/media_base.gyp:base', + '../../event/media_event.gyp:media_event', '../../filters/filters.gyp:filters', ], }, diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 9526d764b4..742598080c 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -178,7 +178,8 @@ Status Segmenter::Initialize(const std::vector& streams, track_type, crypto_period_duration_in_seconds * streams[i]->info()->time_scale(), clear_lead_in_seconds * streams[i]->info()->time_scale(), - nalu_length_size); + nalu_length_size, + muxer_listener_); continue; } diff --git a/packager/mpd/base/dash_iop_mpd_notifier.cc b/packager/mpd/base/dash_iop_mpd_notifier.cc index 57ad646552..cea2156fb2 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier.cc @@ -8,6 +8,7 @@ #include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/mpd_notifier_util.h" +#include "packager/mpd/base/mpd_utils.h" namespace edash_packager { @@ -89,6 +90,8 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info, if (!representation) return false; + representation_id_to_adaptation_set_[representation->id()] = adaptation_set; + SetGroupId(content_type, lang, adaptation_set); *container_id = representation->id(); @@ -123,6 +126,24 @@ bool DashIopMpdNotifier::NotifyNewSegment(uint32_t container_id, return true; } +bool DashIopMpdNotifier::NotifyEncryptionUpdate( + uint32_t container_id, + const std::vector& new_key_id, + const std::vector& new_pssh) { + base::AutoLock auto_lock(lock_); + RepresentationMap::iterator it = representation_map_.find(container_id); + if (it == representation_map_.end()) { + LOG(ERROR) << "Unexpected container_id: " << container_id; + return false; + } + + AdaptationSet* adaptation_set_for_representation = + representation_id_to_adaptation_set_[it->second->id()]; + adaptation_set_for_representation->UpdateContentProtectionPssh( + Uint8VectorToBase64(new_pssh)); + return true; +} + bool DashIopMpdNotifier::AddContentProtectionElement( uint32_t container_id, const ContentProtectionElement& content_protection_element) { diff --git a/packager/mpd/base/dash_iop_mpd_notifier.h b/packager/mpd/base/dash_iop_mpd_notifier.h index bf2a0ade01..1e0e1d4ce0 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.h +++ b/packager/mpd/base/dash_iop_mpd_notifier.h @@ -46,6 +46,10 @@ class DashIopMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size) OVERRIDE; + virtual bool NotifyEncryptionUpdate( + uint32_t container_id, + const std::vector& new_key_id, + const std::vector& new_pssh) OVERRIDE; virtual bool AddContentProtectionElement( uint32_t id, const ContentProtectionElement& content_protection_element) OVERRIDE; @@ -110,6 +114,9 @@ class DashIopMpdNotifier : public MpdNotifier { // Next group ID to use for AdapationSets that can be grouped. int next_group_id_; + + // Maps Representation ID to AdaptationSet. This is for updating the PSSH. + std::map representation_id_to_adaptation_set_; }; } // namespace edash_packager diff --git a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc index 6ba8915fd0..ac89222869 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc @@ -670,6 +670,60 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) { ConvertToMediaInfo(kAudioContent), &unused_container_id)); } +TEST_P(DashIopMpdNotifierTest, UpdateEncryption) { + const char kProtectedContent[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "protected_content {\n" + " content_protection_entry {\n" + " uuid: 'myuuid'\n" + " name_version: 'MyContentProtection version 1'\n" + " pssh: 'pssh1'\n" + " }\n" + " default_key_id: '_default_key_id_'\n" + "}\n" + "container_type: 1\n"; + + DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_, + empty_base_urls_, output_path_); + + scoped_ptr mock_mpd_builder(new MockMpdBuilder(mpd_type())); + + EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_)) + .WillOnce(Return(default_mock_adaptation_set_.get())); + EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0); + EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_)) + .WillOnce(Return(default_mock_representation_.get())); + + uint32_t container_id; + SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs()); + EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent), + &container_id)); + + ::testing::Mock::VerifyAndClearExpectations( + default_mock_adaptation_set_.get()); + + const uint8_t kBogusNewPssh[] = {// "psshsomethingelse" as uint8 array. + 0x70, 0x73, 0x73, 0x68, 0x73, 0x6f, + 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x65, 0x6c, 0x73, 0x65}; + const std::vector kBogusNewPsshVector( + kBogusNewPssh, kBogusNewPssh + arraysize(kBogusNewPssh)); + const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U="; + + EXPECT_CALL(*default_mock_adaptation_set_, + UpdateContentProtectionPssh(kBogusNewPsshInBase64)); + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( + container_id, std::vector(), kBogusNewPsshVector)); +} + INSTANTIATE_TEST_CASE_P(StaticAndDynamic, DashIopMpdNotifierTest, ::testing::Values(MpdBuilder::kStatic, diff --git a/packager/mpd/base/mock_mpd_builder.h b/packager/mpd/base/mock_mpd_builder.h index eed8bde477..716d745322 100644 --- a/packager/mpd/base/mock_mpd_builder.h +++ b/packager/mpd/base/mock_mpd_builder.h @@ -35,6 +35,7 @@ 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_METHOD1(AddRole, void(AdaptationSet::Role role)); MOCK_METHOD1(SetGroup, void(int group_number)); @@ -53,6 +54,7 @@ class MockRepresentation : public Representation { MOCK_METHOD1(AddContentProtectionElement, void(const ContentProtectionElement& element)); + MOCK_METHOD1(UpdateContentProtectionPssh, void(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/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index ee46148d41..8e61df71c0 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -14,6 +14,7 @@ #include #include +#include "packager/base/base64.h" #include "packager/base/files/file_path.h" #include "packager/base/logging.h" #include "packager/base/memory/scoped_ptr.h" @@ -696,6 +697,12 @@ void AdaptationSet::AddContentProtectionElement( RemoveDuplicateAttributes(&content_protection_elements_.back()); } +void AdaptationSet::UpdateContentProtectionPssh( + const std::string& pssh) { + base::AutoLock scoped_lock(lock_); + UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_); +} + void AdaptationSet::AddRole(Role role) { roles_.insert(role); } @@ -1034,6 +1041,12 @@ void Representation::AddContentProtectionElement( RemoveDuplicateAttributes(&content_protection_elements_.back()); } +void Representation::UpdateContentProtectionPssh( + const std::string& pssh) { + base::AutoLock scoped_lock(lock_); + UpdateContentProtectionPsshHelper(pssh, &content_protection_elements_); +} + void Representation::AddNewSegment(uint64_t start_time, uint64_t duration, uint64_t size) { diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index e45453e1cf..3eaaf0beb0 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -30,7 +30,6 @@ #include "packager/mpd/base/content_protection_element.h" #include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/mpd_options.h" -#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/segment_info.h" #include "packager/mpd/base/xml/scoped_xml_ptr.h" @@ -190,6 +189,15 @@ class AdaptationSet { virtual void AddContentProtectionElement( const ContentProtectionElement& element); + /// Update the element for MP4 specific ContentProtection element. + /// If the element does not exist, this will add one. + /// @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); + /// Set the Role element for this AdaptationSet. /// The Role element's is schemeIdUri='urn:mpeg:dash:role:2011'. /// See ISO/IEC 23009-1:2012 section 5.8.5.5. @@ -437,6 +445,15 @@ class Representation { virtual void AddContentProtectionElement( const ContentProtectionElement& element); + /// Update the 'cenc:pssh' element for mp4 specific ContentProtection element. + /// If the element does not exist, this will add one. + /// @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); + /// Add a media (sub)segment to the representation. /// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set /// if this is not called for all Representations. diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 744fbc59fa..9e8433d3f5 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -1252,8 +1252,9 @@ TEST_F(CommonMpdBuilderTest, SetSampleDuration) { representation.media_info_.video_info().frame_duration()); } -// Verify that AdaptationSet::AddContentProtection() works. -TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) { +// Verify that AdaptationSet::AddContentProtection() and +// UpdateContentProtectionPssh() works. +TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtectionAndUpdate) { const char kVideoMediaInfo1080p[] = "video_info {\n" " codec: \"avc1\"\n" @@ -1264,7 +1265,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) { "}\n" "container_type: 1\n"; ContentProtectionElement content_protection; - content_protection.scheme_id_uri = "someuri"; + content_protection.scheme_id_uri = "urn:mpeg:dash:mp4protection:2011"; content_protection.value = "some value"; Element pssh; pssh.name = "cenc:pssh"; @@ -1277,7 +1278,7 @@ TEST_F(CommonMpdBuilderTest, AdaptationSetAddContentProtection) { ConvertToMediaInfo(kVideoMediaInfo1080p))); video_adaptation_set->AddContentProtectionElement(content_protection); - const char kExpectedOutput[] = + const char kExpectedOutput1[] = "\n" "" " " - " " + " " " any value" " " " "; std::string mpd_output; ASSERT_TRUE(mpd_.ToString(&mpd_output)); - EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output)); + EXPECT_TRUE(XmlEqual(kExpectedOutput1, mpd_output)); + + video_adaptation_set->UpdateContentProtectionPssh("new pssh value"); + const char kExpectedOutput2[] = + "\n" + "" + " " + " " + " " + " new pssh value" + " " + " " + " " + " " + ""; + ASSERT_TRUE(mpd_.ToString(&mpd_output)); + EXPECT_TRUE(XmlEqual(kExpectedOutput2, mpd_output)); } // Add one video check the output. diff --git a/packager/mpd/base/mpd_notifier.h b/packager/mpd/base/mpd_notifier.h index 24b9fb7410..c9291465ea 100644 --- a/packager/mpd/base/mpd_notifier.h +++ b/packager/mpd/base/mpd_notifier.h @@ -11,6 +11,7 @@ #define MPD_BASE_MPD_NOTIFIER_H_ #include +#include #include "packager/base/macros.h" @@ -73,6 +74,18 @@ class MpdNotifier { uint64_t duration, uint64_t size) = 0; + /// Notifiers MpdBuilder that there is a new PSSH for the container. + /// This may be called whenever the key has to change, e.g. key rotation. + /// @param container_id Container ID obtained from calling + /// NotifyNewContainer(). + /// @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::vector& new_key_id, + const std::vector& new_pssh) = 0; + /// Adds content protection information to the MPD. /// @param container_id is the nummeric container ID obtained from calling /// NotifyNewContainer(). diff --git a/packager/mpd/base/mpd_notifier_util.cc b/packager/mpd/base/mpd_notifier_util.cc index 0f83e4df68..af51ee9a08 100644 --- a/packager/mpd/base/mpd_notifier_util.cc +++ b/packager/mpd/base/mpd_notifier_util.cc @@ -17,118 +17,6 @@ namespace edash_packager { using media::File; using media::FileCloser; -namespace { - -// Helper function for adding ContentProtection for AdaptatoinSet or -// Representation. -// Works because both classes have AddContentProtectionElement(). -template -void AddContentProtectionElementsHelper(const MediaInfo& media_info, - ContentProtectionParent* parent) { - DCHECK(parent); - if (!media_info.has_protected_content()) - return; - - const MediaInfo::ProtectedContent& protected_content = - media_info.protected_content(); - - const char kEncryptedMp4Uri[] = "urn:mpeg:dash:mp4protection:2011"; - const char kEncryptedMp4Value[] = "cenc"; - - // 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; - if (is_mp4_container) { - ContentProtectionElement mp4_content_protection; - mp4_content_protection.scheme_id_uri = kEncryptedMp4Uri; - mp4_content_protection.value = kEncryptedMp4Value; - if (protected_content.has_default_key_id()) { - std::string key_id_uuid_format; - if (HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { - mp4_content_protection.additional_attributes["cenc:default_KID"] = - key_id_uuid_format; - } else { - LOG(ERROR) << "Failed to convert default key ID into UUID format."; - } - } - - parent->AddContentProtectionElement(mp4_content_protection); - } - - for (int i = 0; i < protected_content.content_protection_entry().size(); - ++i) { - const MediaInfo::ProtectedContent::ContentProtectionEntry& entry = - protected_content.content_protection_entry(i); - if (!entry.has_uuid()) { - LOG(WARNING) - << "ContentProtectionEntry was specified but no UUID is set for " - << entry.name_version() << ", skipping."; - continue; - } - - ContentProtectionElement drm_content_protection; - drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); - if (entry.has_name_version()) - drm_content_protection.value = entry.name_version(); - - if (entry.has_pssh()) { - std::string base64_encoded_pssh; - base::Base64Encode(entry.pssh(), &base64_encoded_pssh); - Element cenc_pssh; - cenc_pssh.name = "cenc:pssh"; - cenc_pssh.content = base64_encoded_pssh; - drm_content_protection.subelements.push_back(cenc_pssh); - } - - parent->AddContentProtectionElement(drm_content_protection); - } - - LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0) - << "The media is encrypted but no content protection specified."; -} - -} // namespace - -// 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 = - StringToLowerASCII(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; -} - bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder) { CHECK(!output_path.empty()); @@ -177,14 +65,11 @@ ContentType GetContentType(const MediaInfo& media_info) { : (has_audio ? kContentTypeAudio : kContentTypeText); } -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* adaptation_set) { - AddContentProtectionElementsHelper(media_info, adaptation_set); -} - -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* representation) { - AddContentProtectionElementsHelper(media_info, representation); +std::string Uint8VectorToBase64(const std::vector& input) { + std::string output; + std::string input_in_string(input.begin(), input.end()); + base::Base64Encode(input_in_string, &output); + return output; } } // namespace edash_packager diff --git a/packager/mpd/base/mpd_notifier_util.h b/packager/mpd/base/mpd_notifier_util.h index 5016a19840..a26d4f671f 100644 --- a/packager/mpd/base/mpd_notifier_util.h +++ b/packager/mpd/base/mpd_notifier_util.h @@ -11,6 +11,7 @@ #define MPD_BASE_MPD_NOTIFIER_UTIL_H_ #include +#include #include "packager/base/base64.h" #include "packager/mpd/base/media_info.pb.h" @@ -25,11 +26,6 @@ enum ContentType { kContentTypeText }; -/// Converts hex data to UUID format. Hex data must be size 16. -/// @param data input hex data. -/// @param uuid_format is the UUID format of the input. -bool HexToUUID(const std::string& data, std::string* uuid_format); - /// Outputs MPD to @a output_path. /// @param output_path is the path to the MPD output location. /// @param mpd_builder is the MPD builder instance. @@ -40,23 +36,8 @@ bool WriteMpdToFile(const std::string& output_path, MpdBuilder* mpd_builder); /// @return content type of the @a media_info. ContentType GetContentType(const MediaInfo& media_info); -/// Adds elements specified by @a media_info to -/// @a adaptation_set. -/// Note that this will add the elements as direct chlidren of AdaptationSet. -/// @param media_info may or may not have protected_content field. -/// @param adaptation_set is the parent element that owns the ContentProtection -/// elements. -void AddContentProtectionElements(const MediaInfo& media_info, - AdaptationSet* adaptation_set); - -/// Adds elements specified by @a media_info to -/// @a representation. -/// @param media_info may or may not have protected_content field. -/// @param representation is the parent element that owns the ContentProtection -/// elements. -void AddContentProtectionElements(const MediaInfo& media_info, - Representation* representation); - +/// Converts uint8 vector into base64 encoded string. +std::string Uint8VectorToBase64(const std::vector& input); } // namespace edash_packager diff --git a/packager/mpd/base/mpd_utils.cc b/packager/mpd/base/mpd_utils.cc index 1ef94bb504..9dc1b45acc 100644 --- a/packager/mpd/base/mpd_utils.cc +++ b/packager/mpd/base/mpd_utils.cc @@ -10,8 +10,6 @@ #include "packager/base/logging.h" #include "packager/base/strings/string_number_conversions.h" -#include "packager/mpd/base/content_protection_element.h" -#include "packager/mpd/base/media_info.pb.h" #include "packager/mpd/base/xml/scoped_xml_ptr.h" namespace edash_packager { @@ -95,4 +93,160 @@ 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 = + StringToLowerASCII(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& pssh, + std::list* conetent_protection_elements) { + for (std::list::iterator protection = + conetent_protection_elements->begin(); + protection != conetent_protection_elements->end(); ++protection) { + if (protection->scheme_id_uri != kEncryptedMp4Scheme) + continue; + + for (std::vector::iterator subelement = + protection->subelements.begin(); + subelement != protection->subelements.end(); ++subelement) { + if (subelement->name == kPsshElementName) { + 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; + return; + } + + // Reaching here means that MP4 specific ContentProtection 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); + return; +} + +namespace { +// 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; + if (is_mp4_container) { + ContentProtectionElement mp4_content_protection; + mp4_content_protection.scheme_id_uri = kEncryptedMp4Scheme; + mp4_content_protection.value = kEncryptedMp4Value; + if (protected_content.has_default_key_id()) { + std::string key_id_uuid_format; + if (HexToUUID(protected_content.default_key_id(), &key_id_uuid_format)) { + mp4_content_protection.additional_attributes["cenc:default_KID"] = + key_id_uuid_format; + } else { + LOG(ERROR) << "Failed to convert default key ID into UUID format."; + } + } + + parent->AddContentProtectionElement(mp4_content_protection); + } + + for (int i = 0; i < protected_content.content_protection_entry().size(); + ++i) { + const MediaInfo::ProtectedContent::ContentProtectionEntry& entry = + protected_content.content_protection_entry(i); + if (!entry.has_uuid()) { + LOG(WARNING) + << "ContentProtectionEntry was specified but no UUID is set for " + << entry.name_version() << ", skipping."; + continue; + } + + ContentProtectionElement drm_content_protection; + drm_content_protection.scheme_id_uri = "urn:uuid:" + entry.uuid(); + if (entry.has_name_version()) + drm_content_protection.value = entry.name_version(); + + if (entry.has_pssh()) { + std::string base64_encoded_pssh; + base::Base64Encode(entry.pssh(), &base64_encoded_pssh); + Element cenc_pssh; + cenc_pssh.name = kPsshElementName; + cenc_pssh.content = base64_encoded_pssh; + drm_content_protection.subelements.push_back(cenc_pssh); + } + + parent->AddContentProtectionElement(drm_content_protection); + } + + LOG_IF(WARNING, protected_content.content_protection_entry().size() == 0) + << "The media is encrypted but no content protection specified."; +} +} // 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 edash_packager diff --git a/packager/mpd/base/mpd_utils.h b/packager/mpd/base/mpd_utils.h index ce8a797ce2..f1bda2b343 100644 --- a/packager/mpd/base/mpd_utils.h +++ b/packager/mpd/base/mpd_utils.h @@ -11,14 +11,25 @@ #include +#include #include +#include "packager/base/base64.h" +#include "packager/base/strings/string_util.h" +#include "packager/mpd/base/content_protection_element.h" +#include "packager/mpd/base/media_info.pb.h" +#include "packager/mpd/base/mpd_builder.h" + namespace edash_packager { class MediaInfo; struct ContentProtectionElement; struct SegmentInfo; +const char kEncryptedMp4Scheme[] = "urn:mpeg:dash:mp4protection:2011"; +const char kPsshElementName[] = "cenc:pssh"; +const char kEncryptedMp4Value[] = "cenc"; + bool HasVODOnlyFields(const MediaInfo& media_info); bool HasLiveOnlyFields(const MediaInfo& media_info); @@ -42,6 +53,34 @@ bool MoreThanOneTrue(bool b1, bool b2, bool b3); bool AtLeastOneTrue(bool b1, bool b2, bool b3); bool OnlyOneTrue(bool b1, bool b2, bool b3); +/// Converts hex data to UUID format. Hex data must be size 16. +/// @param data input hex data. +/// @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. +// If the element does not exist, this will add one. +void UpdateContentProtectionPsshHelper( + const std::string& pssh, + std::list* conetent_protection_elements); + +/// Adds elements specified by @a media_info to +/// @a adaptation_set. +/// Note that this will add the elements as direct chlidren of AdaptationSet. +/// @param media_info may or may not have protected_content field. +/// @param adaptation_set is the parent element that owns the ContentProtection +/// elements. +void AddContentProtectionElements(const MediaInfo& media_info, + Representation* parent); + +/// Adds elements specified by @a media_info to +/// @a representation. +/// @param media_info may or may not have protected_content field. +/// @param representation is the parent element that owns the ContentProtection +/// elements. +void AddContentProtectionElements(const MediaInfo& media_info, + AdaptationSet* parent); + } // namespace edash_packager #endif // MPD_BASE_MPD_UTILS_H_ diff --git a/packager/mpd/base/simple_mpd_notifier.cc b/packager/mpd/base/simple_mpd_notifier.cc index 217efb7bfd..bf659460d3 100644 --- a/packager/mpd/base/simple_mpd_notifier.cc +++ b/packager/mpd/base/simple_mpd_notifier.cc @@ -97,6 +97,20 @@ bool SimpleMpdNotifier::NotifyNewSegment(uint32_t container_id, return true; } +bool SimpleMpdNotifier::NotifyEncryptionUpdate( + uint32_t container_id, + const std::vector& new_key_id, + const std::vector& new_pssh) { + base::AutoLock auto_lock(lock_); + RepresentationMap::iterator it = representation_map_.find(container_id); + if (it == representation_map_.end()) { + LOG(ERROR) << "Unexpected container_id: " << container_id; + return false; + } + it->second->UpdateContentProtectionPssh(Uint8VectorToBase64(new_pssh)); + return true; +} + bool SimpleMpdNotifier::AddContentProtectionElement( uint32_t container_id, const ContentProtectionElement& content_protection_element) { diff --git a/packager/mpd/base/simple_mpd_notifier.h b/packager/mpd/base/simple_mpd_notifier.h index ad33ecfbbe..fae28307c9 100644 --- a/packager/mpd/base/simple_mpd_notifier.h +++ b/packager/mpd/base/simple_mpd_notifier.h @@ -47,6 +47,10 @@ class SimpleMpdNotifier : public MpdNotifier { uint64_t start_time, uint64_t duration, uint64_t size) OVERRIDE; + virtual bool NotifyEncryptionUpdate( + uint32_t container_id, + const std::vector& new_key_id, + const std::vector& new_pssh) OVERRIDE; virtual bool AddContentProtectionElement( uint32_t id, const ContentProtectionElement& content_protection_element) OVERRIDE; diff --git a/packager/mpd/base/simple_mpd_notifier_unittest.cc b/packager/mpd/base/simple_mpd_notifier_unittest.cc index cdc3092633..58e450f2c9 100644 --- a/packager/mpd/base/simple_mpd_notifier_unittest.cc +++ b/packager/mpd/base/simple_mpd_notifier_unittest.cc @@ -236,6 +236,60 @@ TEST_F(SimpleMpdNotifierTest, AddContentProtectionElement) { EXPECT_TRUE(notifier.AddContentProtectionElement(kRepresentationId, element)); } +TEST_P(SimpleMpdNotifierTest, UpdateEncryption) { + const char kProtectedContent[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 640\n" + " height: 360\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "protected_content {\n" + " content_protection_entry {\n" + " uuid: 'myuuid'\n" + " name_version: 'MyContentProtection version 1'\n" + " pssh: 'pssh1'\n" + " }\n" + " default_key_id: '_default_key_id_'\n" + "}\n" + "container_type: 1\n"; + SimpleMpdNotifier notifier(kLiveProfile, empty_mpd_option_, empty_base_urls_, + output_path_); + const uint32_t kRepresentationId = 447834u; + scoped_ptr mock_mpd_builder(DynamicMpdBuilderMock()); + scoped_ptr mock_representation( + new MockRepresentation(kRepresentationId)); + + EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_)) + .WillOnce(Return(default_mock_adaptation_set_.get())); + EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_)) + .WillOnce(Return(mock_representation.get())); + + uint32_t container_id; + SetMpdBuilder(¬ifier, mock_mpd_builder.PassAs()); + EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kProtectedContent), + &container_id)); + + ::testing::Mock::VerifyAndClearExpectations( + default_mock_adaptation_set_.get()); + + // "psshsomethingelse" as uint8 array. + const uint8_t kBogusNewPssh[] = {0x70, 0x73, 0x73, 0x68, 0x73, 0x6f, + 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x65, 0x6c, 0x73, 0x65}; + const std::vector kBogusNewPsshVector( + kBogusNewPssh, kBogusNewPssh + arraysize(kBogusNewPssh)); + const char kBogusNewPsshInBase64[] = "cHNzaHNvbWV0aGluZ2Vsc2U="; + + EXPECT_CALL(*mock_representation, + UpdateContentProtectionPssh(kBogusNewPsshInBase64)); + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( + container_id, std::vector(), kBogusNewPsshVector)); +} + INSTANTIATE_TEST_CASE_P(StaticAndDynamic, SimpleMpdNotifierTest, ::testing::Values(MpdBuilder::kStatic, diff --git a/packager/mpd/util/mpd_writer.cc b/packager/mpd/util/mpd_writer.cc index abf11e45c5..bd6b2434e0 100644 --- a/packager/mpd/util/mpd_writer.cc +++ b/packager/mpd/util/mpd_writer.cc @@ -11,6 +11,7 @@ #include "packager/media/file/file.h" #include "packager/mpd/base/mpd_builder.h" +#include "packager/mpd/base/mpd_utils.h" using edash_packager::media::File;