From 5c8efd332e5f24010ca594c7fe7a821031147aee Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Tue, 7 Jul 2015 17:52:28 -0700 Subject: [PATCH] Pass content protection information to MuxerListener - The UUID and DRM name can be fetched from KeySource. - Add ProtectedContent message to MediaInfo. The message contains basic information for the protected content, such as the default key ID for the content. - The message is required to separate Representations with different content protection information into different AdaptationSets. Change-Id: Ib9dc834ae0abf93b7ca0acdf52a865b1394a4816 --- packager/media/base/key_source.cc | 10 +++++ packager/media/base/key_source.h | 13 ++++++ packager/media/base/widevine_key_source.cc | 4 ++ packager/media/base/widevine_key_source.h | 1 + .../media/event/mpd_notify_muxer_listener.cc | 29 ++++++++++-- .../media/event/mpd_notify_muxer_listener.h | 15 ++++++- packager/media/event/muxer_listener.h | 24 ++++++++-- .../media/event/muxer_listener_internal.cc | 32 ++++++++++++++ .../media/event/muxer_listener_internal.h | 16 +++++++ .../vod_media_info_dump_muxer_listener.cc | 25 +++++++++-- .../vod_media_info_dump_muxer_listener.h | 22 +++++++--- ...media_info_dump_muxer_listener_unittest.cc | 44 ++++++++++++++++--- packager/media/formats/mp4/mp4_muxer.cc | 3 +- packager/media/formats/mp4/segmenter.cc | 8 ++++ packager/mpd/base/media_info.proto | 24 ++++++++++ 15 files changed, 244 insertions(+), 26 deletions(-) diff --git a/packager/media/base/key_source.cc b/packager/media/base/key_source.cc index c4a07eadc5..5ae56209e8 100644 --- a/packager/media/base/key_source.cc +++ b/packager/media/base/key_source.cc @@ -14,6 +14,8 @@ namespace { const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed}; +const char kDefaultUUID[] = ""; +const char kDefaultSystemName[] = ""; } // namespace namespace edash_packager { @@ -67,6 +69,14 @@ Status KeySource::GetCryptoPeriodKey(uint32_t crypto_period_index, return Status(error::UNIMPLEMENTED, ""); } +std::string KeySource::UUID() { + return kDefaultUUID; +} + +std::string KeySource::SystemName() { + return kDefaultSystemName; +} + scoped_ptr KeySource::CreateFromHexStrings( const std::string& key_id_hex, const std::string& key_hex, diff --git a/packager/media/base/key_source.h b/packager/media/base/key_source.h index fa3f363d25..4fe5eb4fa0 100644 --- a/packager/media/base/key_source.h +++ b/packager/media/base/key_source.h @@ -83,6 +83,19 @@ class KeySource { TrackType track_type, EncryptionKey* key); + /// Returns the UUID of the key source in human readable form. + /// UUIDs are listed here: + /// http://dashif.org/identifiers/protection/ + /// @return UUID of the key source, empty string if not specified. + virtual std::string UUID(); + + /// Returns the name, and possibly with a version number, of the key source. + /// (This would be the ContentProtection@value attribute in the MPD. DASH-IF- + /// IOP v3.0 recommends this to be the DRM system and version name in human + /// readable from.) + /// @return the name of the key source, empty string if not specified. + virtual std::string SystemName(); + /// Create KeySource object from hex strings. /// @param key_id_hex is the key id in hex string. /// @param key_hex is the key in hex string. diff --git a/packager/media/base/widevine_key_source.cc b/packager/media/base/widevine_key_source.cc index f310de713a..e8859277f0 100644 --- a/packager/media/base/widevine_key_source.cc +++ b/packager/media/base/widevine_key_source.cc @@ -236,6 +236,10 @@ Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index, return GetKeyInternal(crypto_period_index, track_type, key); } +std::string WidevineKeySource::UUID() { + return "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; +} + void WidevineKeySource::set_signer(scoped_ptr signer) { signer_ = signer.Pass(); } diff --git a/packager/media/base/widevine_key_source.h b/packager/media/base/widevine_key_source.h index 9c91948950..c4fb254133 100644 --- a/packager/media/base/widevine_key_source.h +++ b/packager/media/base/widevine_key_source.h @@ -43,6 +43,7 @@ class WidevineKeySource : public KeySource { virtual Status GetCryptoPeriodKey(uint32_t crypto_period_index, TrackType track_type, EncryptionKey* key) OVERRIDE; + virtual std::string UUID() OVERRIDE; /// @} /// Set signer for the key source. diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index aa2ca47456..58ff4fbdac 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -19,7 +19,7 @@ namespace edash_packager { namespace media { MpdNotifyMuxerListener::MpdNotifyMuxerListener(MpdNotifier* mpd_notifier) - : mpd_notifier_(mpd_notifier), notification_id_(0) { + : mpd_notifier_(mpd_notifier), notification_id_(0), is_encrypted_(false) { DCHECK(mpd_notifier); DCHECK(mpd_notifier->dash_profile() == kOnDemandProfile || mpd_notifier->dash_profile() == kLiveProfile); @@ -32,12 +32,23 @@ void MpdNotifyMuxerListener::SetContentProtectionSchemeIdUri( scheme_id_uri_ = scheme_id_uri; } +void MpdNotifyMuxerListener::OnEncryptionInfoReady( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::vector& default_key_id, + const std::vector& pssh) { + 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; +} + void MpdNotifyMuxerListener::OnMediaStart( const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, - ContainerType container_type, - bool is_encrypted) { + ContainerType container_type) { scoped_ptr media_info(new MediaInfo()); if (!internal::GenerateMediaInfo(muxer_options, stream_info, @@ -48,7 +59,17 @@ void MpdNotifyMuxerListener::OnMediaStart( return; } - if (is_encrypted) { + if (is_encrypted_) { + internal::SetContentProtectionFields( + content_protection_uuid_, content_protection_name_version_, + default_key_id_, pssh_, media_info.get()); + } + + if (is_encrypted_) { + // TODO(rkuroiwa): When MediaInfo's content protection fields are processed + // in MpdBuilder (e.g. content_protection_uuid, default_key_id) then skip + // this step if scheme_id_uri_'s UUID == content_protection_uuid_. + // Also consider removing SetContentProtectionSchemeIdUri(). if (!internal::AddContentProtectionElements( container_type, scheme_id_uri_, media_info.get())) { LOG(ERROR) << "Failed to add content protection elements."; diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 1d8cb119b0..d580881b0b 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -36,11 +36,15 @@ class MpdNotifyMuxerListener : public MuxerListener { /// @name MuxerListener implementation overrides. /// @{ + virtual void OnEncryptionInfoReady( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::vector& default_key_id, + const std::vector& pssh) OVERRIDE; virtual void OnMediaStart(const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, - ContainerType container_type, - bool is_encrypted) OVERRIDE; + ContainerType container_type) OVERRIDE; virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE; virtual void OnMediaEnd(bool has_init_range, uint64_t init_range_start, @@ -61,6 +65,13 @@ class MpdNotifyMuxerListener : public MuxerListener { scoped_ptr media_info_; std::string scheme_id_uri_; + bool is_encrypted_; + // Storage for values passed to OnEncryptionInfoReady(). + std::string content_protection_uuid_; + std::string content_protection_name_version_; + std::string default_key_id_; + std::string pssh_; + DISALLOW_COPY_AND_ASSIGN(MpdNotifyMuxerListener); }; diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index e3e338a9d0..6712563d6e 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -6,6 +6,8 @@ // // Event handler for events fired by Muxer. +// TODO(rkuroiwa): Document using doxygen style comments. + #ifndef MEDIA_EVENT_MUXER_LISTENER_H_ #define MEDIA_EVENT_MUXER_LISTENER_H_ @@ -34,7 +36,24 @@ class MuxerListener { virtual ~MuxerListener() {}; - // Called when muxing starts. This event happens before any other events. + // Called when the media's encryption information is ready. This should be + // called before OnMediaStart(), if the media is encrypted. + // All the parameters may be empty just to notify that the media is encrypted. + // |content_protection_uuid| is one of the UUIDs listed here + // http://dashif.org/identifiers/protection/. This should be in human + // readable form. + // |content_protection_name_version| is the DRM system and version name. + // 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. + // |pssh| is the whole 'pssh' box. + virtual void OnEncryptionInfoReady( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::vector& default_key_id, + const std::vector& pssh) = 0; + + // Called when muxing starts. // For MPEG DASH Live profile, the initialization segment information is // available from StreamInfo. // |time_scale| is a reference time scale that overrides the time scale @@ -42,8 +61,7 @@ class MuxerListener { virtual void OnMediaStart(const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, - ContainerType container_type, - bool is_encrypted) = 0; + ContainerType container_type) = 0; /// Called when the average sample duration of the media is determined. /// @param sample_duration in timescale of the media. diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index ca6cc5e7c3..a0539c0f61 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -201,6 +201,8 @@ bool SetVodInformation(bool has_init_range, return true; } +// TODO(rkuroiwa): Move this logic to MpdBuilder? MuxerListener probably doesn't +// need to know this. bool AddContentProtectionElements(MuxerListener::ContainerType container_type, const std::string& user_scheme_id_uri, MediaInfo* media_info) { @@ -237,6 +239,36 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type, return true; } +void SetContentProtectionFields( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::string& default_key_id, + const std::string& pssh, + MediaInfo* media_info) { + DCHECK(media_info); + MediaInfo::ProtectedContent* protected_content = + media_info->mutable_protected_content(); + + if (!default_key_id.empty()) + protected_content->set_default_key_id(default_key_id); + + if (content_protection_uuid.empty() && + content_protection_name_version.empty() && pssh.empty()) { + return; + } + + MediaInfo::ProtectedContent::ContentProtectionEntry* entry = + protected_content->add_content_protection_entry(); + if (!content_protection_uuid.empty()) + entry->set_uuid(content_protection_uuid); + + if (!content_protection_name_version.empty()) + entry->set_name_version(content_protection_name_version); + + if (!pssh.empty()) + entry->set_pssh(pssh); +} + } // namespace internal } // namespace media } // namespace edash_packager diff --git a/packager/media/event/muxer_listener_internal.h b/packager/media/event/muxer_listener_internal.h index a3e28d82ef..201eda62ed 100644 --- a/packager/media/event/muxer_listener_internal.h +++ b/packager/media/event/muxer_listener_internal.h @@ -54,6 +54,22 @@ bool AddContentProtectionElements(MuxerListener::ContainerType container_type, const std::string& user_scheme_id_uri, MediaInfo* media_info); +/// @param content_protection_uuid is the UUID of the content protection +/// in human readable form. +/// @param content_protection_name_version is the DRM name and verion. +/// @param default_key_id is the key ID for this media in hex (i.e. non-human +/// readable, typically 16 bytes.) +/// @param pssh is the pssh for the media in hex (i.e. non-human readable, raw +/// 'pssh' box.) +/// @param media_info is where the content protection information is stored and +/// cannot be null. +void SetContentProtectionFields( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::string& default_key_id, + const std::string& pssh, + MediaInfo* media_info); + } // namespace internal } // namespace media } // namespace edash_packager 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 a52624f82e..e9e020e870 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -20,7 +20,7 @@ namespace media { VodMediaInfoDumpMuxerListener::VodMediaInfoDumpMuxerListener( const std::string& output_file_name) - : output_file_name_(output_file_name) {} + : output_file_name_(output_file_name), is_encrypted_(false) {} VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {} @@ -29,12 +29,23 @@ void VodMediaInfoDumpMuxerListener::SetContentProtectionSchemeIdUri( scheme_id_uri_ = scheme_id_uri; } +void VodMediaInfoDumpMuxerListener::OnEncryptionInfoReady( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::vector& default_key_id, + const std::vector& pssh) { + 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; +} + void VodMediaInfoDumpMuxerListener::OnMediaStart( const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, - ContainerType container_type, - bool is_encrypted) { + ContainerType container_type) { DCHECK(muxer_options.single_segment); media_info_.reset(new MediaInfo()); if (!internal::GenerateMediaInfo(muxer_options, @@ -46,7 +57,13 @@ void VodMediaInfoDumpMuxerListener::OnMediaStart( return; } - if (is_encrypted) { + if (is_encrypted_) { + internal::SetContentProtectionFields( + content_protection_uuid_, content_protection_name_version_, + default_key_id_, pssh_, media_info_.get()); + } + + if (is_encrypted_) { if (!internal::AddContentProtectionElements( container_type, scheme_id_uri_, media_info_.get())) { LOG(ERROR) << "Failed to add content protection elements."; 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 144957366d..552d822d1f 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -36,11 +36,16 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { /// @name MuxerListener implementation overrides. /// @{ - virtual void OnMediaStart(const MuxerOptions& muxer_options, - const StreamInfo& stream_info, - uint32_t time_scale, - ContainerType container_type, - bool is_encrypted) OVERRIDE; + virtual void OnEncryptionInfoReady( + const std::string& content_protection_uuid, + const std::string& content_protection_name_version, + const std::vector& default_key_id, + const std::vector& pssh) OVERRIDE; + virtual void OnMediaStart( + const MuxerOptions& muxer_options, + const StreamInfo& stream_info, + uint32_t time_scale, + ContainerType container_type) OVERRIDE; virtual void OnSampleDurationReady(uint32_t sample_duration) OVERRIDE; virtual void OnMediaEnd(bool has_init_range, uint64_t init_range_start, @@ -63,6 +68,13 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { std::string scheme_id_uri_; scoped_ptr media_info_; + bool is_encrypted_; + // Storage for values passed to OnEncryptionInfoReady(). + std::string content_protection_uuid_; + std::string content_protection_name_version_; + std::string default_key_id_; + std::string pssh_; + DISALLOW_COPY_AND_ASSIGN(VodMediaInfoDumpMuxerListener); }; 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 20d37f44a9..3fd535533f 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 @@ -20,6 +20,19 @@ namespace { const bool kEnableEncryption = true; +// '_default_key_id_' (length 16). +const uint8_t kBogusDefaultKeyId[] = {0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x69, 0x64, 0x5f}; +// 'pssh'. Not a valid pssh box. +const uint8_t kInvalidPssh[] = { + 0x70, 0x73, 0x73, 0x68 +}; + +// This should be in the uuid field of the protobuf. This is not a valid UUID +// format but the protobof generation shouldn't care. +const char kTestUUID[] = "myuuid"; +const char kTestContentProtectionName[] = "MyContentProtection version 1"; } // namespace namespace edash_packager { @@ -178,11 +191,21 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { MuxerOptions muxer_options; SetDefaultMuxerOptionsValues(&muxer_options); const uint32_t kReferenceTimeScale = 1000; - listener_->OnMediaStart(muxer_options, - stream_info, - kReferenceTimeScale, - MuxerListener::kContainerMp4, - enable_encryption); + if (enable_encryption) { + std::vector bogus_default_key_id( + kBogusDefaultKeyId, + kBogusDefaultKeyId + arraysize(kBogusDefaultKeyId)); + + // This isn't a valid pssh box but the MediaInfo protobuf creator + // shouldn't worry about it. + std::vector invalid_pssh(kInvalidPssh, + kInvalidPssh + arraysize(kInvalidPssh)); + + listener_->OnEncryptionInfoReady(kTestUUID, kTestContentProtectionName, + bogus_default_key_id, invalid_pssh); + } + listener_->OnMediaStart(muxer_options, stream_info, kReferenceTimeScale, + MuxerListener::kContainerMp4); } void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { @@ -285,7 +308,16 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) { "reference_time_scale: 1000\n" "container_type: 1\n" "media_file_name: 'test_output_file_name.mp4'\n" - "media_duration_seconds: 10.5\n"; + "media_duration_seconds: 10.5\n" + "protected_content {\n" + " content_protection_entry {\n" + " uuid: 'myuuid'\n" + " name_version: 'MyContentProtection version 1'\n" + " pssh: 'pssh'\n" + " }\n" + " default_key_id: '_default_key_id_'\n" + "}\n"; + ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput)); } diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index bc3ec080c6..f2386e3f13 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -244,8 +244,7 @@ void MP4Muxer::FireOnMediaStartEvent() { muxer_listener()->OnMediaStart(options(), *streams().front()->info(), timescale, - MuxerListener::kContainerMp4, - encryption_key_source() != NULL); + MuxerListener::kContainerMp4); } void MP4Muxer::FireOnMediaEndEvent() { diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 419e3eae81..ea9b004b7b 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -15,6 +15,7 @@ #include "packager/media/base/media_stream.h" #include "packager/media/base/muxer_options.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/event/muxer_listener.h" #include "packager/media/event/progress_listener.h" #include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/key_rotation_fragmenter.h" @@ -196,6 +197,13 @@ Status Segmenter::Initialize(const std::vector& streams, if (moov_->pssh.empty()) { moov_->pssh.resize(1); moov_->pssh[0].raw_box = encryption_key->pssh; + + // Also only one default key id. + if (muxer_listener_) { + muxer_listener_->OnEncryptionInfoReady( + encryption_key_source->UUID(), encryption_key_source->SystemName(), + encryption_key->key_id, encryption_key->pssh); + } } fragmenters_[i] = new EncryptingFragmenter( diff --git a/packager/mpd/base/media_info.proto b/packager/mpd/base/media_info.proto index eeffc609c2..894633e9fc 100644 --- a/packager/mpd/base/media_info.proto +++ b/packager/mpd/base/media_info.proto @@ -55,6 +55,26 @@ message MediaInfo { optional string language = 2; } + message ProtectedContent { + message ContentProtectionEntry { + // Human readable UUID of the DRM. + optional string uuid = 1; + // Human readable DRM name and version string. + // e.g. "My Content Protection v1.0" + optional string name_version = 2; + // The raw 'pssh' box for the media. + optional bytes pssh = 3; + } + + // The default key ID for the encrypted media. + optional bytes default_key_id = 1; + repeated ContentProtectionEntry content_protection_entry = 2; + } + + // TODO(rkuroiwa): Remove this. element that must be added + // should be done by directly using the MpdBuilder interface. + // Use this to specify ContentProtection elements that should be set in + // the MPD, if ContentProtectionEntry is not sufficient. message ContentProtectionXml { message AttributeNameValuePair { optional string name = 1; @@ -85,6 +105,10 @@ message MediaInfo { optional TextInfo text_info = 4; repeated ContentProtectionXml content_protections = 5; + // This is set if the content is protected with a content protection, + // i.e. encrypted. + optional ProtectedContent protected_content = 15; + // This is the reference time scale if there are multiple VideoInfo and/or // AudioInfo. optional uint32 reference_time_scale = 13;